5月28日 03:29
如何实现 Web 图片懒加载?
面试官问:如何实现 Web 图片懒加载?
图片懒加载的核心思路:图片不在视口内时不加载,滚动到接近视口时再加载,减少首屏请求数和带宽消耗。
答案
现代前端有三种主流实现方式,按推荐优先级排列:
1. 原生 loading="lazy"(首选)
html<img src="image.jpg" loading="lazy" alt="描述文字" />
一行搞定。Chrome 76+、Edge、Firefox 均支持,Safari 15.4+ 也已支持。浏览器自动根据视口距离判断加载时机,无需 JS。
关键细节:对首屏内的图片不要加 loading="lazy",否则可能延迟 LCP。建议只对视口下方的图片使用。
2. Intersection Observer(兼容方案)
javascriptconst observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; observer.unobserve(img); // 加载完停止观察 } }); }, { rootMargin: '200px' } // 提前 200px 开始加载,减少白屏 ); document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
rootMargin 可以让图片在进入视口前就开始加载,用户体验更平滑。threshold 控制交叉比例触发阈值,懒加载场景一般用默认 0 即可。
3. scroll 事件监听(不推荐)
javascript// 需要配合 throttle 使用 function throttle(fn, delay) { let timer = null; return function (...args) { if (timer) return; timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); }; } window.addEventListener('scroll', throttle(() => { document.querySelectorAll('img[data-src]').forEach(img => { if (img.getBoundingClientRect().top < window.innerHeight + 200) { img.src = img.dataset.src; } }); }, 200));
为什么不推荐:scroll 事件在主线程频繁触发,即使 throttle 也无法避免 getBoundingClientRect 触发强制重排(reflow)。Intersection Observer 在浏览器合成器线程异步执行,完全不阻塞主线程,性能差距显著。
方案对比
| 方案 | 兼容性 | 性能 | 代码量 | SEO 友好 |
|---|---|---|---|---|
loading="lazy" | Chrome 76+, Safari 15.4+ | 最优 | 1 行 | 最佳 |
| Intersection Observer | 所有现代浏览器 | 优 | ~10 行 | 需配合 noscript |
| scroll 监听 | 全兼容 | 差 | ~20 行 | 需配合 noscript |
追问
Intersection Observer 和 scroll 监听有什么区别?
核心区别在执行线程和触发机制:
- 执行线程:scroll 监听在主线程同步执行,频繁触发即使 throttle 也会有性能开销;Intersection Observer 在浏览器合成器线程异步执行,不阻塞主线程
- 位置计算:scroll 监听需要手动调用
getBoundingClientRect,每次调用触发强制重排;Intersection Observer 由浏览器内部计算,无重排开销 - 触发精度:scroll 监听依赖 throttle 间隔,可能错过或延迟;Intersection Observer 在元素进入视口时精确触发
- 内存管理:Intersection Observer 的
unobserve语义清晰;scroll 监听需要手动维护已加载列表
懒加载对 SEO 有影响吗?
有影响。搜索引擎爬虫一般不执行 JS,data-src 中的图片地址对爬虫不可见。解决方案:
loading="lazy"原生方式:爬虫能识别src属性,SEO 影响最小<noscript>提供替代:
html<img data-src="image.jpg" alt="描述" /> <noscript><img src="image.jpg" alt="描述" /></noscript>
- SSR/SSG 时直接渲染
src:服务端渲染时输出完整src,客户端再按需懒加载 - 配合
srcset+sizes:让浏览器和爬虫都能选择合适尺寸
懒加载图片会导致 CLS 问题吗?
会。图片加载前高度为 0,加载后撑开页面内容产生布局偏移——这是 CLS 扣分的主要原因。解决办法:
css/* 方案一:aspect-ratio(推荐) */ .img-wrapper { aspect-ratio: 16 / 9; width: 100%; } /* 方案二:padding-bottom 百分比技巧(兼容旧浏览器) */ .img-wrapper { position: relative; width: 100%; padding-bottom: 56.25%; /* 16:9 = 9/16 * 100% */ } .img-wrapper img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
也可以用固定高度的占位容器或低分辨率模糊占位图(LQIP)预留空间。
首屏图片应该懒加载吗?
不应该。首屏图片是 LCP 关键元素,懒加载会延迟加载时机,直接拖慢 LCP 指标。正确做法:
- 首屏图片直接设置
src,并加上fetchpriority="high"提升优先级 - 对首屏以下图片使用
loading="lazy"或 Intersection Observer - 可以结合
preload提前加载首屏关键图片
html<!-- 首屏:不懒加载,提升优先级 --> <link rel="preload" as="image" href="hero.jpg" /> <img src="hero.jpg" fetchpriority="high" alt="首屏主图" /> <!-- 非首屏:懒加载 --> <img src="below-fold.jpg" loading="lazy" alt="描述" />
最佳实践总结
- 优先使用原生
loading="lazy",兼容性已足够 - 需要精细控制时用 Intersection Observer +
rootMargin - 避免对首屏图片懒加载
- 必须预留图片空间防止 CLS
- SEO 敏感场景用
<noscript>或 SSR 兜底 - 废弃 scroll 监听方案