图片未加载时应使用带宽高和aspect-ratio的div容器配合::before伪元素+内联svg背景图实现骨架屏,真实图片绝对定位覆盖并监听onload平滑过渡,动画仅限opacity/transform且需适配dpr与prefers-reduced-motion。

图片未加载时怎么显示骨架屏
骨架屏本质是用纯 CSS 模拟内容区域的占位结构,不是靠 JS 渲染 DOM 节点。关键在于:图片本身还没加载,<img> 标签的 src 还没触发请求,所以必须用容器元素(比如 <div>)承载骨架样式,再通过 <code>background-image 或伪元素模拟图层。
常见错误是直接给 <img> 加背景色或边框——它默认是 inline 元素,不设 display: block 和宽高,背景根本不会显示;更糟的是有些框架(如 Vue)会把 <img> 包在 wrapper 里,结果骨架样式写在错误层级上。
- 用
<div class="img-placeholder"></div>作为容器,显式设置width、height和aspect-ratio - 骨架用
::before伪元素实现,避免污染语义结构 - 背景图用
background-image: url("data:image/svg+xml,...")内联 SVG,免去额外 HTTP 请求 - 不要依赖
img[src]选择器做骨架切换——它只在src存在时匹配,而懒加载常先清空src或用data-src
如何让骨架屏随图片加载自然过渡
过渡的核心不是“淡入”,而是「视觉一致性」:骨架的尺寸、圆角、阴影要和真实图片完全一致,否则加载后会跳动。CSS 动画只能控制 opacity 或 transform,不能改变背景图本身,所以得靠两层叠加 + visibility 控制。
典型翻车场景:用 opacity: 0 → 1 动画盖在骨架上,但图片加载完成前就触发了动画,导致骨架消失太快,用户看到白屏闪一下;或者用 display: none 切换,破坏文档流引发重排。
- 真实图片用
<img>,绝对定位盖在骨架容器上,初始opacity: 0、visibility: hidden - 监听
img.onload后,移除骨架类名,同时设visibility: visible+opacity: 1,用transition: opacity 0.2s ease - 失败回退时(
img.onerror),保留骨架并加一层文字提示,比如 “加载失败” —— 不要用alt文本顶替,它不会触发重绘 - 别用
will-change: opacity,移动端 Safari 对该属性处理不稳定,容易卡顿
CSS 动画怎么避免影响懒加载性能
骨架屏动画不是为了炫技,是为了降低用户对等待的感知。但过度动画会持续触发重绘,尤其在低端安卓机上,可能拖慢图片解码和渲染。最稳妥的方式是只动 opacity,不动 background-position 或 gradient stop。
常见陷阱:用 background: linear-gradient(...) + animation: bgShift 1.5s infinite 模拟光效扫过,看似高级,实则每帧都触发合成层更新;更隐蔽的问题是,某些构建工具(如 Vite)会把内联 SVG 中的 <animate></animate> 标签当无效语法过滤掉,导致骨架“冻住”。
- 骨架动画仅限
opacity或transform: translateX(),两者都走 GPU 合成,不触发布局 - SVG 骨架里禁用
<animate></animate>、<set></set>等 SMIL 标签,改用 CSS@keyframes控制伪元素 - 动画时长控制在 1.2s 内,超过 1.5s 用户会误判为卡死
- 用
prefers-reduced-motion: reduce媒体查询关掉动画,iOS/Android 系统级设置会生效
背景图骨架怎么兼容不同 DPR 和尺寸
用 background-image 实现骨架时,如果只写一个 SVG,高 DPR 屏幕下会模糊;如果写多倍图,又增加请求数。最优解是用矢量 SVG + CSS 缩放,但必须注意 viewBox 和 width/height 的配合关系。
容易被忽略的一点:SVG 中的 px 单位在缩放时不会自动适配,比如 <rect x="10" y="10" width="20" height="20"></rect> 在 background-size: 200% 下会变大四倍,而不是清晰放大两倍。
- SVG 必须带
viewBox="0 0 W H",且内部所有尺寸用无单位数值(等价于 user units) - 容器设
background-size: cover或具体值(如100% 100%),避免用contain导致留白 - 用
image-set()函数提供多倍背景图(Chrome/Firefox 支持),fallback 到单 SVG:background-image: image-set(url(1x.svg) 1x, url(2x.svg) 2x), url(fallback.svg) - 测试时务必在真机开启“显示缩放区域”(Android 开发者选项)或用 iOS 的“放大显示”功能,验证 DPR 切换是否正常
骨架屏最难的不是画得多像,而是让它在各种网络波动、设备缩放、系统设置下都不突兀。很多时候问题不出在 CSS 写法,而出在容器尺寸没锁死、动画没降级、或者 SVG 的坐标系没对齐。











