用 IntersectionObserver 做懒加载与曝光统计
早期做懒加载靠监听 scroll 事件,再逐个调用 getBoundingClientRect()。这会强制同步布局,滚动时极易掉帧。IntersectionObserver 把这类判断交给浏览器,回调在空闲时批量触发。
基本用法
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.src = e.target.dataset.src;
io.unobserve(e.target); // 加载一次即可
}
});
}, { rootMargin: '200px 0px' });
document.querySelectorAll('img[data-src]').forEach(el => io.observe(el));
rootMargin 提前 200px 触发,用户滚到时图片已经就位,不会看到空白跳变。
三个参数的含义
root:参照容器,默认是视口;rootMargin:扩大或缩小判定边界,语法同 CSS margin;threshold:交叉比例阈值,0 表示刚露出一点,1 表示完全可见。
曝光统计通常用 threshold: [0, 0.5],在 50% 可见时上报,避免用户快速划过也被计数。
曝光统计的去重
曝光只应上报一次。用一个 WeakSet 记录已上报元素,元素被回收时集合也随之释放:
const seen = new WeakSet();
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.intersectionRatio >= 0.5 && !seen.has(e.target)) {
seen.add(e.target);
report(e.target.dataset.id);
}
});
}, { threshold: [0.5] });
兼容与降级
现代浏览器支持良好,但老版本 Safari 需要 polyfill。降级方案是先判断 'IntersectionObserver' in window,不支持则直接加载全部资源——功能可用性优先于性能优化。
不要过度使用
首屏关键图片不要懒加载,否则会拖慢 LCP。判断标准是:这张图是否在用户不滚动的情况下就能看到。能看到,就正常加载。
另外,IntersectionObserver 只告诉你「可见性变化」,不告诉你「是否被遮挡」「是否在后台标签页」。需要更精细的可见性判断时,配合 document.visibilityState 使用。