骨架屏防闪烁的核心是“提前占位+平滑过渡”:用容器设宽高与aspect-ratio撑开空间,配合linear-gradient扫光动画和opacity渐变完成加载过渡,确保样式完全一致。

用骨架屏替代图片闪烁,核心是“提前占位 + 平滑过渡”,不是等图片加载完再显示,而是从页面渲染第一刻起,就让占位区域拥有和真实图片完全一致的尺寸、形状与视觉节奏——这样加载完成时才不会跳、不抖、不闪。
必须用容器撑开空间,不能依赖 img 自身
图片闪烁的根源,是 img 元素在 src 为空或未加载时宽高为 0×0,导致父容器塌陷,后续图片插入瞬间强行撑开布局,引发下方内容下移(CLS 偏移爆表)。所以:
- 把 img 单独拎出来,用 div 容器包裹,例如
<div class="img-skeleton"><img></div> - 容器必须设 width 和 aspect-ratio(如
aspect-ratio: 16 / 9),确保空间被稳稳撑开 - 老 Safari 需 fallback:
padding-top: 56.25%+position: relative,子元素position: absolute布局 - 所有骨架容器加 overflow: hidden,防止渐变动画滑出圆角或边框露出底色
用 linear-gradient + animation 做扫光效果,别用 opacity 闪
骨架屏的“呼吸感”来自横向扫光动画,不是简单透明度明暗切换。opacity 闪烁会显得机械、廉价,而扫光更贴近真实加载节奏:
- 背景写法:
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%) - 关键三要素:background-size: 200% 100%(必须固定像素或百分比,禁用 rem/vw)、background-position: -200% 0(起始偏移)、background-repeat: no-repeat
- @keyframes 中只动
background-position,例如to { background-position: 200% 0; } - 动画时长建议 1.5s–2s,timing-function 用
cubic-bezier(0.4, 0, 0.6, 1)更自然
加载完成时靠 opacity 过渡,不删 DOM 不改 display
图片加载完成那一瞬最易暴露问题。粗暴替换、display 切换、visibility 控制都会中断渲染流程,造成闪动:
- 给 img 设初始样式:
opacity: 0; transition: opacity 0.3s ease; - 监听
img.onload后,只添加一个 class(如is-loaded),该 class 仅含opacity: 1 - 若骨架是
::before伪元素,记得在img.is-loaded::before中写display: none,避免叠在图上 - 绝对不用
visibility: hidden → visible,它不触发动画,且可能触发重排
细节对齐决定是否真“无缝”
哪怕骨架和真实图片只差 1px 圆角、0.5px 边框、2% 的阴影扩散值,加载完成瞬间也会明显跳动:
- 骨架容器的 border-radius、box-shadow、border-width 必须和真实图片样式完全一致
- 如果真实图片有 object-fit: cover,骨架容器也要加
overflow: hidden并确保子 img 尺寸匹配 - SSR 场景下,服务端需直接输出骨架 HTML,避免客户端 JS 插入导致首屏闪一下
- 移动端测试重点:iOS Safari 对 background-size 百分比的支持较弱,优先用 px 或明确 % 值











