IntersectionObserver 是浏览器原生异步回调机制,用于监听元素进入/离开视口,性能优于 scroll + getBoundingClientRect();它不触发强制同步布局,支持 root、threshold、rootMargin 配置,需处理加载失败、重复绑定、内存泄漏等问题,并在不支持时降级为节流 scroll + requestIdleCallback。

IntersectionObserver 是什么,为什么不用 getBoundingClientRect
它不是轮询检测,而是浏览器原生的异步回调机制,性能远好于 scroll + getBoundingClientRect()。尤其在长列表或滚动频繁的页面里,后者容易触发强制同步布局(Layout Thrashing),造成卡顿。
关键点:它只在元素进入/离开视口时才通知,且不阻塞主线程;而 getBoundingClientRect() 每次调用都可能触发重排。
基本用法:监听图片是否进入视口并加载
核心是创建一个 IntersectionObserver 实例,传入回调函数和配置项,再用 observe() 绑定目标 元素。
-
root:默认为浏览器视口;设为某个容器元素时,就以该容器为“视口”边界 -
threshold:0~1 的数组,表示目标元素多少比例进入 root 时触发回调(如[0, 0.25, 0.5, 0.75, 1]) -
rootMargin:类似 CSS margin,可提前触发(如"100px"表示元素距离视口还有 100px 就开始加载)
const imgObserver = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imgObserver.unobserve(img); // 加载完就停止监听
}
});
},
{
rootMargin: '100px', // 提前加载,防滚动过快出现空白
threshold: 0.1
}
);
document.querySelectorAll('img.lazy').forEach(img => {
imgObserver.observe(img);
});
常见坑:图片加载失败后没 fallback、重复绑定、未解绑
懒加载不是加个 src 就完事——网络异常、路径错误、CORS 都会让 img 显示为空白,且 IntersectionObserver 不会二次触发。
立即学习“Java免费学习笔记(深入)”;
- 务必监听
img.onerror,降级显示占位图或提示文字 - 避免对同一张图多次
observe()(比如组件重复渲染时没清理旧 observer) -
unobserve()要及时调用;否则即使图片已加载,observer 仍持有引用,影响内存回收 - 服务端返回的 HTML 若含
src,要确保它为空或为占位图,否则浏览器会提前发起请求,失去懒加载意义
兼容性与降级方案:Safari 12.1+ 和 IE 完全不支持
如果必须支持 Safari 12 或更早版本,不能直接用 IntersectionObserver。可用 scroll + requestIdleCallback 组合做轻量降级,但要注意节流。
简单判断:
if ('IntersectionObserver' in window) {
// 使用 IntersectionObserver
} else {
// 降级:监听 scroll,用 getBoundingClientRect + throttle
// 注意:避免在回调中直接操作 DOM,优先用 requestIdleCallback 延后执行
}
真正麻烦的不是写降级逻辑,而是混合使用时容易漏掉 unobserve 或重复绑定;建议项目里统一抽象成一个 useLazyImage Hook 或工具函数,把判断、绑定、错误处理、清理全包进去。










