懒加载 loading
图片懒加载(Lazy Loading)是一种性能优化策略:只有当图片即将进入浏览器视口(Viewport)时,才开始加载图片资源。这可以显著减少首屏加载时间,节省带宽。
HTML5 引入了 loading 属性,为 <img> 和 <iframe> 提供原生懒加载支持。在此之前,开发者需要借助 JavaScript(如 IntersectionObserver)来实现相同的功能。
前置知识
阅读本节前,建议先了解:image-map 图像映射
基础概念
懒加载(Lazy Loading)是一种性能优化策略:只有当图片即将进入浏览器视口时,才开始加载图片资源。这可以显著减少首屏加载时间,节省带宽,降低服务器负载。
HTML5 原生 loading 属性让懒加载变得极其简单——只需一个属性值,无需任何 JavaScript。浏览器原生实现比 JS 方案更高效,因为它能在 HTML 解析阶段就做出加载决策。
语法
loading 属性
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>懒加载</title>
</head>
<body>
<h1>loading 属性</h1>
<!-- 立即加载(默认行为) -->
<img src="hero.jpg"
alt="首屏大图"
loading="eager"
width="1200" height="800">
<hr>
<!-- 懒加载:图片进入视口附近才开始下载 -->
<img src="photo-1.jpg"
alt="文章配图 1"
loading="lazy"
width="800" height="600">
<img src="photo-2.jpg"
alt="文章配图 2"
loading="lazy"
width="800" height="600">
<!-- 多张长列表图片 -->
<img src="photo-3.jpg" alt="文章配图 3" loading="lazy" width="800" height="600">
<img src="photo-4.jpg" alt="文章配图 4" loading="lazy" width="800" height="600">
<img src="photo-5.jpg" alt="文章配图 5" loading="lazy" width="800" height="600">
</body>
</html>详细说明
loading="lazy" 的工作原理
html
<!--
浏览器处理流程:
1. 解析 HTML,遇到 loading="lazy" 的 img
2. 不立即请求 src 指向的资源
3. 当用户滚动到图片接近视口(通常有距离缓冲区)
4. 浏览器发起请求,加载图片
5. 图片加载完成后显示
注意:设置了 width/height 才能正确预留空间
-->
<!-- 首屏图片:使用 eager 或不设置 loading -->
<img src="hero-banner.jpg"
alt="首屏横幅"
loading="eager"
width="1200" height="400">
<!-- 长内容中的图片:使用 lazy -->
<img src="content-image-1.jpg"
alt="内容图片 1"
loading="lazy"
width="800" height="600">
<img src="content-image-2.jpg"
alt="内容图片 2"
loading="lazy"
width="800" height="600">loading="lazy" 的触发距离
浏览器在图片距离视口还有一定距离时就开始加载(预加载缓冲区):
| 浏览器 | 触发距离 | 说明 |
|---|---|---|
| Chrome | 视口高度 x 1250px(约) | 高分辨率屏幕距离更远 |
| Firefox | 视口高度 x (5% + 125px) | 较近触发 |
| Safari | 视口高度 x 300px(约) | 最近触发 |
| Edge | 与 Chrome 相同 | Chromium 内核 |
loading 属性值对比
| 值 | 行为 | 适用场景 |
|---|---|---|
lazy | 进入视口附近时加载 | 非首屏图片 |
eager | 立即加载 | 首屏重要图片 |
| 不设置 | 等同于 eager | 默认行为 |
何时使用 lazy vs eager
html
<!-- eager:首屏可见区域的内容 -->
<header>
<img src="logo.png" alt="Logo" width="120" height="40" loading="eager">
</header>
<main>
<!-- eager:首屏大图(用户第一眼看到) -->
<img src="hero.jpg" alt="首屏大图" width="1200" height="600" loading="eager">
<!-- lazy:用户需要滚动才能看到的内容 -->
<img src="article-img-1.jpg" alt="文章图片 1" width="800" height="500" loading="lazy">
<img src="article-img-2.jpg" alt="文章图片 2" width="800" height="500" loading="lazy">
<img src="article-img-3.jpg" alt="文章图片 3" width="800" height="500" loading="lazy">
<!-- lazy:页脚内容 -->
<img src="partner-logo-1.png" alt="合作伙伴 1" width="100" height="40" loading="lazy">
<img src="partner-logo-2.png" alt="合作伙伴 2" width="100" height="40" loading="lazy">
</main>JavaScript IntersectionObserver 方案
对于更精细的控制,可以使用 JavaScript 的 IntersectionObserver:
javascript
// JavaScript:使用 IntersectionObserver 实现懒加载
document.addEventListener('DOMContentLoaded', function() {
const lazyImages = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const img = entry.target;
// 将 data-src 赋值给 src
img.src = img.dataset.src;
// 图片开始加载后停止观察
observer.unobserve(img);
}
});
}, {
// rootMargin:提前 200px 开始加载
rootMargin: '200px 0px',
// threshold:元素可见 0% 时触发
threshold: 0
});
lazyImages.forEach(function(img) {
observer.observe(img);
});
});html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS 懒加载</title>
<style>
.lazy-img {
background-color: #f0f0f0;
min-height: 200px;
}
</style>
</head>
<body>
<!-- 使用 data-src 存储真实地址,src 使用占位图 -->
<img class="lazy-img"
src="placeholder-1x1.png"
data-src="real-photo.jpg"
alt="通过 JS 懒加载的图片"
width="800" height="600">
<script src="lazy-load.js"></script>
</body>
</html>HTML loading vs JS IntersectionObserver
| 对比项 | loading="lazy" | IntersectionObserver |
|---|---|---|
| 实现难度 | 极简(一个属性) | 需要 JS 代码 |
| 浏览器支持 | Chrome 77+、Firefox 75+、Edge | 更广泛 |
| 控制粒度 | 简单(开/关) | 精细(距离、阈值) |
| 降级处理 | 忽略属性,立即加载 | 需要 noscript 回退 |
| 适用元素 | <img>、<iframe> | 任意 DOM 元素 |
实战示例
图片列表懒加载
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片列表懒加载</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
.photo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
.photo-grid img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 8px;
background: #f0f0f0;
}
</style>
</head>
<body>
<h1>摄影作品</h1>
<p>下拉查看更多作品,图片会在接近视口时自动加载。</p>
<div class="photo-grid">
<!-- 首屏 1~2 张图片:eager -->
<img src="photo-01.jpg" alt="作品 1" width="400" height="300" loading="eager">
<img src="photo-02.jpg" alt="作品 2" width="400" height="300" loading="eager">
<!-- 非首屏:lazy -->
<img src="photo-03.jpg" alt="作品 3" width="400" height="300" loading="lazy">
<img src="photo-04.jpg" alt="作品 4" width="400" height="300" loading="lazy">
<img src="photo-05.jpg" alt="作品 5" width="400" height="300" loading="lazy">
<img src="photo-06.jpg" alt="作品 6" width="400" height="300" loading="lazy">
<img src="photo-07.jpg" alt="作品 7" width="400" height="300" loading="lazy">
<img src="photo-08.jpg" alt="作品 8" width="400" height="300" loading="lazy">
<img src="photo-09.jpg" alt="作品 9" width="400" height="300" loading="lazy">
<img src="photo-10.jpg" alt="作品 10" width="400" height="300" loading="lazy">
</div>
</body>
</html>注意事项
loading="lazy"在首屏图片上使用会延迟首屏渲染,适得其反- 必须配合
width/height使用,否则预留空间可能不正确 - Safari 对
loading="lazy"的支持较晚(iOS 15.4+ / Safari 15.4+) loading="lazy"适用于<img>和<iframe>,不适用于<video>的poster- SSR/预渲染场景中,
loading="lazy"可能在 HTML 生成阶段就被加载 - 过度使用
loading="lazy"(如每页只有 3 张图)意义不大
最佳实践
- 首屏可见的图片使用
loading="eager"(或不设置 loading) - 首屏以下的图片使用
loading="lazy" - 始终设置
width/height避免布局偏移 - 结合
decoding="async"进一步优化 - 对于不支持
loading="lazy"的旧浏览器,使用IntersectionObserver作为 polyfill - Lighthouse 可以检测懒加载使用是否合理
下一节
继续学习:图片格式与选择