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(兼容方案)

javascript
const 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 中的图片地址对爬虫不可见。解决方案:

  1. loading="lazy" 原生方式:爬虫能识别 src 属性,SEO 影响最小
  2. <noscript> 提供替代
html
<img data-src="image.jpg" alt="描述" /> <noscript><img src="image.jpg" alt="描述" /></noscript>
  1. SSR/SSG 时直接渲染 src:服务端渲染时输出完整 src,客户端再按需懒加载
  2. 配合 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 指标。正确做法:

  1. 首屏图片直接设置 src,并加上 fetchpriority="high" 提升优先级
  2. 对首屏以下图片使用 loading="lazy" 或 Intersection Observer
  3. 可以结合 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="描述" />

最佳实践总结

  1. 优先使用原生 loading="lazy",兼容性已足够
  2. 需要精细控制时用 Intersection Observer + rootMargin
  3. 避免对首屏图片懒加载
  4. 必须预留图片空间防止 CLS
  5. SEO 敏感场景用 <noscript> 或 SSR 兜底
  6. 废弃 scroll 监听方案
标签:前端