loading="lazy"与IntersectionObserver不能混用,因二者会并发触发同一图片的两次加载:前者在DOM解析时预请求src,后者在回调中再赋值data-src,导致重复请求、闪屏及onload触发两次。

为什么 loading="lazy" 和 IntersectionObserver 不能混用
混用会触发重复请求——loading="lazy 在 DOM 解析阶段就发起图片请求,而 IntersectionObserver 回调里又执行一次 img.src = img.dataset.src,同一张图被加载两次。浏览器不会因为 src 已赋值就跳过 loading="lazy" 的预解析逻辑。
常见错误现象:Network 面板看到同一张图出现两个 200 请求;首屏图片闪一下又重载;img 元素 onload 触发两次。
- 只对
<img>和<iframe>生效,且必须带src属性才启动懒加载逻辑 -
loading="lazy"是声明式、不可编程的,无法控制提前量、失败重试或加载状态样式 - 若同时写了
src和data-src,loading="lazy"会先加载src,Observer 再加载data-src,完全背离设计意图
如何让 IntersectionObserver 真正“懒”而不抢跑
默认构造 new IntersectionObserver(callback) 会在实例创建后立刻检查所有已 observe 的元素是否在视口内,导致首屏图片“立即加载”,根本不是懒加载。
关键在 threshold 和 rootMargin 的配合:
立即学习“前端免费学习笔记(深入)”;
- 必须传
threshold: [0](数组形式),而非threshold: 0(数字)——后者在某些浏览器中仍可能触发初始检查 -
rootMargin: "0px 0px 200px 0px"让元素距离视口底部还有 200px 时就开始加载,避免滚动过快出现空白 - 首次回调中务必检查
entry.isIntersecting === true,否则滚动离开又返回时可能重复赋值 - 加载完成后必须调用
observer.unobserve(img),否则持续监听浪费 CPU 且可能因 DOM 变动引发异常
data-src 为什么不能用 src="about:blank"
看似清空 src 能阻止预加载,但 src="about:blank" 或透明 base64 占位图在部分浏览器(如旧版 Safari、某些 WebView)中仍会发起请求,甚至附带 referrer,破坏懒加载初衷。
更稳妥的占位方案:
- 用最小 GIF:
src="data:image/gif;base64,R0lGODdhAQABAPAAAP8AAAAAACwAAAAAAQABAAACAkQBADs" - 或直接留空:
<img alt="">,再靠 CSS 控制尺寸(必须有 width/height 或aspect-ratio) - 绝对不要写
src=""—— 某些浏览器会解析为当前页面 URL 并发起无效请求
动态插入内容时 Observer 不生效怎么办
通过 fetch() 或 innerHTML 插入新 <img class="lazy"> 后,Observer 实例并不自动接管这些新节点,它们始终处于未观察状态,永远不加载。
正确做法是显式调用 observe():
- 每次插入后遍历新节点:
newImages.forEach(img => observer.observe(img)) - 避免重复 observe:插入前先过滤掉已含
dataset.loaded或已移除class="lazy"的元素 - 如果使用模板字符串生成 HTML,确保
data-src属性被正确写出,而不是漏写或拼错成data-srcs - 注意:Vue/React 组件内动态渲染的图片,需在
mounted或useEffect中手动 observe,不能依赖初始 DOM 查询
真正防阻塞的关键不在“多加一个 API”,而在彻底放弃对 src 的信任——只要它出现在 HTML 字符串里,浏览器就可能提前加载。一切真实资源地址,必须藏在 data-* 里,由 JS 在精确时机亲手释放。



















