最简单有效的方案是使用原生loading="lazy"属性,现代浏览器(chrome 76+、edge 79+、firefox 75+、safari 15.4+)原生支持,无需js;但必须设置宽高防止布局偏移,且需避免父容器使用transform+overflow:hidden导致滚动根异常。

用 loading="lazy" 是最简单有效的方案
现代浏览器(Chrome 76+、Edge 79+、Firefox 75+、Safari 15.4+)原生支持 loading 属性,只需给 <img> 加上 loading="lazy",浏览器就会自动在图片进入视口前延迟加载。它不依赖 JS,不触发重排,兼容性够用且零成本。
常见错误是加了但没生效——通常因为图片没有明确的宽高(导致布局抖动,部分浏览器会降级为 eager),或父容器用了 overflow: hidden + transform 等导致滚动根节点异常。
-
<img src="photo.jpg" loading="lazy" style="max-width:90%" style="max-width:90%" alt="...">—— 必须设width/height(内联或 CSS 均可),否则 Safari 可能忽略 lazy - 避免在
<img>父级上同时用transform和overflow: hidden,这会让 Chrome 误判滚动容器,导致图片永远不触发加载 -
loading="eager"仅用于首屏关键图(如 banner 主图),不要滥用;未声明该属性时默认行为是eager
fallback 方案:IntersectionObserver 手动控制 src
当需要支持老浏览器(如 IE、旧版 Safari),或想精细控制加载时机(比如提前 200px 开始加载),就得用 JS。直接操作 src 是最稳妥的方式,比 srcset 或 data-src 更少歧义。
注意:别用 getBoundingClientRect() 轮询判断位置,性能差且不准;也别在 scroll 事件里直接查元素位置——必须节流或改用 IntersectionObserver。
- 观察器需设置
rootMargin: "200px"提前触发,避免快速滚动时“闪现”空白 - 一旦
isIntersecting === true,立刻赋值img.src = img.dataset.src,然后调用unobserve(img)防止重复执行 - 记得给
<img>加decoding="async",让浏览器用后台线程解码,减少主线程阻塞
哪些图片不该 lazy?
懒加载不是万能的,加错地方反而损害体验和 SEO。核心判断标准是:是否影响首屏内容呈现、是否会被用户立即看到。
- 首屏图片(
<img>在 HTML 中位于视口内,或通过position: fixed覆盖在顶部)—— 必须loading="eager"或不加该属性 - 背景图(
background-image)——loading属性对其无效,需用 CSS@media (prefers-reduced-data)或 JS 动态插入 - 通过
<picture></picture>+<source></source>切换格式的图——每个<source></source>不继承loading,只作用于外层<img>,所以仍可正常 lazy
和服务端渲染(SSR)/静态站点一起用要注意什么?
服务端生成的 HTML 里如果写了 loading="lazy",但客户端 JS 又手动替换了 src,可能造成两次请求(一次浏览器预加载,一次 JS 加载)。这不是 bug,但浪费带宽。
- Next.js、Nuxt 等框架默认对
<img>做优化,会自动加loading="lazy"并处理占位,此时你无需额外封装 Observer - 若自己写 SSR,确保服务端不渲染
src(只留data-src),等客户端 JS 激活后再赋值——但这样会丢失 SEO 图片语义,不推荐 - 更稳妥的做法是:服务端完整输出
src+loading="lazy",靠浏览器原生行为加载;JS 仅作为 fallback 存在,用if ('loading' in HTMLImageElement.prototype)检测后跳过逻辑
width/height 缺失引发的 layout shift,以及 transform 容器对 IntersectionObserver 的干扰——这两点不解决,懒加载就只是个摆设。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











