原生 loading="lazy" 已足够满足图片和 iframe 懒加载需求,无需自行封装组件;其仅支持 img 和 iframe 标签,需配合 src 使用,Chrome 76+ 等主流浏览器支持,首屏图片建议禁用。

HTML 懒加载用 loading="lazy" 就够,别自己写组件
浏览器原生支持的 loading="lazy" 已覆盖绝大多数图片和 iframe 的延迟加载需求。自己封装“懒加载组件”反而容易引入滚动监听抖动、首屏判断偏差、SSR 不兼容等问题。
常见错误现象:IntersectionObserver 监听未加 threshold 导致图片进视口一半才触发;服务端渲染时直接渲染占位图,客户端又重复加载;img 标签同时写了 src 和 data-src,原生 lazy 被绕过。
-
loading="lazy"仅对<img>和<iframe>生效,不支持自定义元素或背景图 - 必须配合
src(或srcset)使用,不能只写data-src—— 否则浏览器根本不会发起请求 - Chrome 76+、Firefox 75+、Safari 15.4+ 支持,iOS Safari 15.4+ 才真正稳定,旧版本会自动退化为正常加载(无害)
- 首屏图片建议不加
loading="lazy",避免因渲染顺序或 fetch 优先级导致关键资源延后
非 <img> / <iframe> 元素怎么懒加载
比如卡片列表、广告位、评论区这些 HTML 块级结构,原生不支持 lazy,得靠 IntersectionObserver。但重点不是“怎么监听”,而是“什么时候该加载”。
使用场景:SPA 中路由切换后动态插入的模块;长列表中折叠区域展开前的内容;第三方 widget(如客服按钮)延迟初始化。
立即学习“前端免费学习笔记(深入)”;
- 不要在
componentDidMount或onMounted里直接加载,要等元素真正进入视口再触发 -
threshold设为0.01(而非0),避免因小数像素计算误差导致错过回调 - 观察器实例必须手动
unobserve(),否则内存泄漏 —— 尤其在 React/Vue 列表项频繁销毁重建时 - 服务端渲染(SSR)下,初始 HTML 应包含最小可用结构(如骨架屏),而不是空容器,否则首屏不可见且 SEO 友好性归零
IntersectionObserver 替代方案:CSS content-visibility 更轻量
如果目标是“让浏览器跳过渲染和布局”,而不是“延迟请求资源”,content-visibility: auto 是更底层、更省 CPU 的选择,尤其适合长文档或设置页这类静态内容区块。
性能影响明显:开启后,滚动到可视区域外的 <section> 不参与 layout、paint,DOM 仍存在,但渲染开销趋近于零。
- 仅 Chrome 85+、Edge 85+、Firefox 103+ 支持,Safari 尚未支持(截至 Safari 17.5)
- 必须配合
contain-intrinsic-size使用,否则高度塌陷 —— 比如设为contain-intrinsic-size: 500px - 不能用于有 transform/opacity 动画的容器,会强制解除优化
- 与
loading="lazy"完全正交:一个管资源请求时机,一个管渲染管线跳过
为什么别用 setTimeout 或 requestIdleCallback 做 HTML 懒加载
这两种方式本质是“时间驱动”,而懒加载的核心诉求是“空间可见性”。用错维度,问题一堆。
典型错误现象:页面刚打开就执行 setTimeout(..., 200) 加载评论区,结果用户还没滚动就触发了;requestIdleCallback 在低端机上可能永远不回调,导致内容白屏。
-
setTimeout无法感知用户是否真的需要这部分内容,纯靠猜,网络差时反而加重首屏压力 -
requestIdleCallback的空闲时间不可控,且 Firefox 已废弃该 API,Chrome 也仅限主线程空闲,不保证执行 - 如果真要兜底降级(比如不支持
IntersectionObserver),应 fallback 到 scroll + getBoundingClientRect,而不是时间函数
复杂点在于:懒加载从来不是“要不要做”的问题,而是“在哪一层做、由谁控制、失败后如何降级”的权衡。原生能力能 cover 的,就别自己造轮子;轮子必须造时,先想清楚它是在对抗什么 —— 是带宽?渲染耗时?还是 JS 执行阻塞?答案不同,方案就完全不同。



















