SVG占位图最稳,因其可缩放、体积小(约200字节)、自带宽高比控制,避免布局抖动;需动态注入viewBox并配合CSS淡入过渡,防止闪跳与FOUC。

占位图不是“先放个灰块”就完事——它必须和真实图片保持宽高比、支持淡入、且不能触发重排。否则懒加载反而拖慢首屏。
为什么 SVG 占位图比 CSS 或 base64 更稳
用 background-color 或固定尺寸的 div 做占位,一旦真实图片宽高比不同,就会拉伸/裁剪,造成布局抖动(layout shift);而 base64 占位图体积大、无法缩放适配,还会阻塞解析。SVG 占位图是唯一能同时满足三个条件的方案:可缩放、体积小、自带宽高比控制。
- SVG 本质是矢量代码,
<svg viewBox="0 0 w h">中的w和h可直接从图片原始尺寸提取,确保占位区域 1:1 匹配 - 浏览器渲染 SVG 占位图不触发 layout,只走 paint 阶段,对 FPS 几乎无影响
- 一个 200×150 的 SVG 占位图压缩后通常仅 200 字节左右,远小于同等效果的 base64 图片(常超 2KB)
如何从 data-src 推导 SVG viewBox 并注入 DOM
不能靠后端预生成或硬编码 viewBox —— 实际项目中图片尺寸千差万别,必须在 JS 初始化时动态提取。关键是:**不要等 img 渲染后再读取 naturalWidth/naturalHeight,那会触发 reflow**。
- 把宽高信息提前写进
data-width和data-height属性,服务端渲染或构建时注入(例如 CMS 输出时带上) - 若无法改后端,可用
new Image()异步预读尺寸,但必须加防抖 + 缓存,避免重复请求 - 生成 SVG 字符串后,用
src="data:image/svg+xml,..."直接赋给img.src,不要用 background-image
示例(简化版):
const getSvgPlaceholder = (w, h) => `<svg viewBox="0 0 ${w} ${h}" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#f0f0f0"/></svg>`;
const img = document.querySelector('img[data-src]');
const w = img.dataset.width;
const h = img.dataset.height;
if (w && h) {
img.src = `data:image/svg+xml,${encodeURIComponent(getSvgPlaceholder(w, h))}`;
}
切换真实图片时怎么避免闪跳和 FOUC
直接改 img.src 会导致瞬间空白(尤其网络慢时),必须配合 CSS 控制过渡和加载状态。
- 给
img添加 class 如lazy-img--loading,初始设opacity: 0.3; transition: opacity 0.3s ease - 在
img.onload回调里移除 loading class 并设opacity: 1,而非依赖 IntersectionObserver 回调时机 - 务必加
img.onerror处理,失败时 fallback 到纯色占位,避免空框 - 慎用
observer.unobserve(img)后立刻移除 class —— 如果图片加载失败,用户将看不到任何内容
rootMargin 和 threshold 怎么配才不白忙活
配错这两个值,等于把懒加载做成“伪懒加载”:要么加载太晚(用户已滑过)、要么加载太早(失去节省带宽意义)。
-
rootMargin: "200px"是较通用的起步值,适合中速滚动;移动端可放宽到"300px",因触屏惯性更强 -
threshold: 0.01表示只要露出 1%,就触发加载 —— 这比0更可靠,避免因四舍五入导致始终不触发 - 绝对不要设
threshold: [0, 0.5, 1]然后监听多次回调:多数场景只需加载一次,多阈值徒增判断逻辑,还可能引发重复加载 - 如果页面有吸顶导航栏,记得在
rootMargin里补上导航高度,例如"200px 0px -64px"(下边距负值表示提前更多)
最易被忽略的一点:IntersectionObserver 不会自动处理图片加载失败后的重试或降级。你得自己在 onload/onerror 里接管后续逻辑,否则用户永远卡在灰色 SVG 上。


















