为什么要懒加载
图片是网页中最占带宽的资源。如果页面中有 20 张图片,且每张 200KB,首屏就需要下载 4MB 数据。而用户可能只看了前几张就离开了——这意味着大量的带宽和加载时间被浪费了。
懒加载的核心思路是:只加载用户视口内和即将进入视口的图片,其余图片等用户滚动到附近再加载。
原生方案:loading=“lazy”
现代浏览器已原生支持图片懒加载,只需在 <img> 标签添加属性:
1<img src="image.webp" loading="lazy" alt="描述">
Chrome、Firefox、Edge 均已支持。这是最简单的实现,无需 JavaScript。
不足:
- Safari 支持较晚(iOS 15.4+)
- 无法控制加载阈值(浏览器自行决定何时开始加载)
- 无法自定义占位图
- 不支持背景图片懒加载
Intersection Observer 方案
Intersection Observer API 提供了更灵活的控制能力。它会在目标元素进入/离开视口(或指定祖先元素)时触发回调。
基础实现
1const observer = new IntersectionObserver((entries) => {
2 entries.forEach(entry => {
3 if (entry.isIntersecting) {
4 const img = entry.target;
5 img.src = img.dataset.src;
6 img.onload = () => img.classList.add('loaded');
7 observer.unobserve(img);
8 }
9 });
10}, {
11 rootMargin: '200px', // 提前 200px 开始加载
12 threshold: 0
13});
14
15document.querySelectorAll('img[data-src]').forEach(img => {
16 observer.observe(img);
17});
HTML 结构:
1<img data-src="/images/photo.webp"
2 src="/images/loading.gif"
3 alt="图片描述"
4 class="lazy-img">
关键参数
| 参数 | 说明 |
|---|---|
rootMargin | 视口扩展区域,200px 表示图片在进入视口前 200px 就开始加载 |
threshold | 可见比例阈值,0 表示只要有一个像素进入就触发 |
加载状态
为提升体验,图片通常经历三种状态:
1.lazy-img {
2 opacity: 0;
3 transition: opacity 0.3s ease;
4 background: #f0f0f0;
5}
6.lazy-img.loaded {
7 opacity: 1;
8}
9.lazy-img.error {
10 /* 加载失败时显示 */
11 background: #fff0f0 url('/images/broken.svg') center/48px no-repeat;
12}
- 加载中:显示占位图(loading.gif)或骨架屏
- 加载完成:透明度过渡动画显示真实图片
- 加载失败:显示错误占位图
在 Hugo 中集成
在 Hugo 模板中,通过 shortcode 或自定义 render hook 自动为 Markdown 图片添加懒加载标记:
1{{/* layouts/_default/_markup/render-image.html */}}
2<img data-src="{{ .Destination | safeURL }}"
3 src="/images/loading.gif"
4 alt="{{ .Text }}"
5 class="lazy-img"
6 loading="lazy">
这样所有通过  插入的图片都会自动拥有懒加载能力。
性能对比
以一个包含 15 张大图的页面为例:
| 指标 | 无懒加载 | 原生 loading=“lazy” | Intersection Observer |
|---|---|---|---|
| 首屏图片请求数 | 15 | 3-5 | 3-5 |
| 首屏图片总大小 | 3.2MB | 0.6MB | 0.6MB |
| LCP 时间 | 4.2s | 1.8s | 1.7s |
| 可配置性 | - | 低 | 高 |
进阶:响应式图片懒加载
结合 <picture> 元素和 srcset,可以实现真正的响应式懒加载:
1<picture>
2 <source data-srcset="/images/photo-800.webp" media="(min-width: 800px)">
3 <source data-srcset="/images/photo-400.webp" media="(min-width: 400px)">
4 <img data-src="/images/photo-200.webp"
5 src="/images/loading.gif"
6 alt="响应式图片"
7 class="lazy-img">
8</picture>
移动端加载小图,桌面端加载大图,进一步节省带宽。
总结
推荐策略:
- 简单场景:直接使用
loading="lazy"原生属性 - 需要精细控制:使用 Intersection Observer,设置
rootMargin提前加载 - 最佳实践:两者结合,
loading="lazy"作为兜底,JS 方案做增强
留言评论
期待你的想法评论加载中