以前提到图片懒加载,第一反应就是引入 intersection observer 或者 jquery.lazyload。现在呢?老老实实写两个 HTML 属性就完事了——loading="lazy" 和 decoding="async"。这俩属性说起来简单,但很多人都只是听说过,没有真正理解怎么用。今天从原理到实战,把图片加载的最后一公里安排得明明白白。
从一张图片说起
浏览器加载 HTML 时,遇到 <img> 后,会先下载图片,然后解码成位图,最后再绘制到页面上。这个过程在大量图片的页面里,会严重影响首屏速度。尤其是移动端,网络又慢,图片一多就白屏。
而 loading="lazy" 就是告诉浏览器:这张图片先别急着下载,等它快要进入视口时再开始加载。简单说,它就是浏览器内置的懒加载能力,完全不用写 JS。
decoding="async" 则是让浏览器在解码图片时不要阻塞其余 HTML 的渲染。默认解码方式可能是同步的,这意味着浏览器可能在解码完一张图片前,不继续渲染下面的内容。用了 async 后,解码过程放到后台,页面先展示文字和结构,图片什么时候解码完了什么时候出现。
基础用法就是加两个属性
<img src="big-photo.jpg" alt="风景" loading="lazy" decoding="async">
就这一句话。没有数据属性,没有回调函数。浏览器自己判断元素位置,自己调度网络请求。而且图片未进入视口时不会加载,进入视口前大约 300px 就开始预加载,体验很丝滑。
实战:改造一个多图页面
我搞了一个简单的画廊页面,里面放了 20 张 800×600 的测试图。改造前,一打开页面就同时发送 20 个请求,加载完页面得好几秒。改造后,甚至网络面板只显示首屏附近的几个请求。
第一步:先把所有图片加上懒加载和解码异步。下面是改造后的一个典型模板:
<div class="gallery">
<img src="photo1.jpg" alt="作品 1" loading="lazy" decoding="async" width="800" height="600">
<img src="photo2.jpg" alt="作品 2" loading="lazy" decoding="async" width="800" height="600">
<img src="photo3.jpg" alt="作品 3" loading="lazy" decoding="async" width="800" height="600">
<!-- 后面还有 17 张 -->
</div>
注意我特意加了 width 和 height。因为如果不给图片占位尺寸,浏览器不知道图片的宽高比,页面会在图片加载后发生布局偏移(CLS),影响性能评分。而咱们做的是性能优化,CLS 可不能糟糕。
第二步:对于首屏第一张图(或关键图),不要加 lazy,正常加载,例如:
<img src="hero.jpg" alt="头图" width="1920" height="800" fetchpriority="high">
第一张轮播图或者 Logo,最好用 fetchpriority="high" 提示浏览器优先加载,而 lazy 会让它延迟,看不到首屏效果。比例子中的 hero 图就不该加 lazy。
怎么验证懒加载生效了?
打开 Chrome 开发者工具,切到 Network 面板,筛选 Img 资源,然后重新加载页面。你会看到只有视口附近的图片被加载了。当你滚动页面,新的图片才不断出现。如果滚动到底部,你还能看到有些图片根本没有请求,这就是懒加载起效了。
再切到 Performance 面板,观察“解码图片”的时间,你会发现如果用了 decoding="async",Decode Image 任务不会阻塞主线程渲染,交互响应更快。
一个看似简单但很重要的细节:动态图片怎么办?
懒加载并不是神奇的魔法,如果你在页面加载后通过 JS 动态插入一张图片,浏览器不会自动应用加载行为,即使它带有 loading="lazy" 属性?实际上如果图片本来就带这个属性,它是有效的。比如你用 innerHTML 插入一段包含 <img loading="lazy"> 的 HTML,浏览器会正确识别。但如果你只是给一个已有的 img 对象赋值属性,那就要看时机。所以最好的做法是在插入 HTML 时就带上属性。
还有人说:loading="lazy" 对 iframe 也有效?没错,iframe 也支持,建议给那些不是首屏的视频嵌入或广告 iframe 加上。
解码 async 和懒加载是否总有冲突?
没有冲突。它们是两个不同维度的优化。懒加载控制“什么时候开始下载图片”,decoding 控制“下载完成后,如何解码”。你可以单独使用一个,也可以组合使用。事实上,Google 的 PageSpeed Insights 经常建议同时添加这两个属性。
但是要注意:decoding="async" 对于很小的图片(比如头像图标)不会带来明显提升,而且还可能让图片延迟出现(因为异步解码会和其他任务排队)。如果你使用 CSS 设计关键的背景图,那用不上这个属性,只有 <img> 元素才有。
还需要考虑 JS 回退吗?
如果你需要支持非常老的浏览器(比如 IE),那真得用 polyfill。但现在是 2025 年,loading="lazy" 已经得到所有现代浏览器的支持(包括 Safari 15.4+)。至于 decoding="async",Safari 也在 15.4 后支持了。所以直截了当地用吧,不要为老古董浪费时间。
一个完整的示例页面
下面是一个包含不同图片的完整页面,你可以直接复制保存为 HTML 文件测试效果。我完全没有写任何样式,只是为了演示属性。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>原生懒加载演示</title>
</head>
<body>
<h1>向下滚动查看懒加载效果</h1>
<img src="https://picsum.photos/id/10/800/600" alt="示例图1" loading="lazy" decoding="async" width="800" height="600">
<p>这是一段描述文字,它应该立刻显示。</p>
<img src="https://picsum.photos/id/20/800/600" alt="示例图2" loading="lazy" decoding="async" width="800" height="600">
<p>中间内容,比较长。</p>
<img src="https://picsum.photos/id/30/800/600" alt="示例图3" loading="lazy" decoding="async" width="800" height="600">
<p>继续滚动……</p>
<img src="https://picsum.photos/id/40/800/600" alt="示例图4" loading="lazy" decoding="async" width="800" height="600">
<p>下面还有</p>
<img src="https://picsum.photos/id/50/800/600" alt="示例图5" loading="lazy" decoding="async" width="800" height="600">
</body>
</html>
你最好自己找一些大图片替换链接,这样效果更明显。加载这个页面时,注意控制台 Network 面板,你会发现首屏其实只加载了第一张图(甚至第一张都不加载,如果它不在视口内)。而文字和后面的内容会立即显示。这就是原生懒加载的价值所在。
再深入一点:CSS 背景图是否也有类似属性?
很遗憾,CSS 背景图没有原生的 loading="lazy" 属性。如果你有背景图需求,可以改用 <img> 元素作为背景,或者使用 JS。但今天的话题是纯 HTML,所以就不展开了。
总结与最佳实践
- 首屏关键图片(Logo、banner)不要用
loading="lazy",给它们加上fetchpriority="high"更好。 - 非首屏图片、小型轮播图以外的所有
<img>都加上loading="lazy"和decoding="async"。 - 记得给每张图设定
width和height,避免累加 CLS。 - 如果是动态插入的图片,确保字符串中包含这两个属性。如果使用 JS 的
Image()对象,无法直接设置,但这种方式不常用,建议还是用 HTML 字符串。 - 别忘给视频或 iframe 也加上
loading="lazy"。
现在很多前端打包工具会自动给 <img> 加上这些属性,比如 Next.js 的 Image 组件会自动处理。如果你在写原生 HTML,或者后端渲染模板,一定要手写上去。
最后说一句:以前我们写的懒加载库可能有各种自定义的占位图、错误处理,但原生方案简单粗暴,性能却更好,因为浏览器调度网络比 JS 更智能。与其纠结要不要用某个库,不如直接跑起来试试。
以后做页面,看到图片就条件反射地加这两属性,已经长在肌肉里了。

