Intersection Observer监听多个元素时为什么回调重复触发?
我在用Intersection Observer给图片列表做懒加载,当快速滚动时发现有些图片的回调会被重复触发,导致重复加载。已经给每个图片绑定了唯一的observer,也检查了entries.isIntersecting条件,但问题依旧存在。这是为什么呢?
代码结构大概是这样的:const observer = new IntersectionObserver((entries) => { ... }),然后遍历图片元素执行observer.observe(img)。当滚动回顶部时,之前加载过的图片又会再次触发回调…
const observer = new IntersectionObserver((entries, self) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('加载:', entry.target.src);
entry.target.src = entry.target.dataset.src;
self.unobserve(entry.target); // 已经加了这行
}
});
}, { threshold: 0 });
document.querySelectorAll('.lazy-img').forEach(img => {
observer.observe(img);
});
奇怪的是,当把self.unobserve(entry.target)注释掉后,问题反而消失了,但这样又会导致多次渲染…
你用了
self.unobserve(entry.target)这个操作本身是没问题的,但问题是当你快速滚动时,unobserve会立即将目标元素从观察列表中移除,而浏览器的布局和渲染可能还没完全跟上,导致同一个元素在短时间内又被重新判定为进入视口,从而再次触发回调。解决方法其实很简单,别急着用
unobserve,而是通过给元素加一个标记来避免重复处理。比如你可以直接在 DOM 元素上挂一个自定义属性,标记它是否已经加载过了:这样做的好处是,即使因为快速滚动导致回调被多次触发,也不会重复执行加载逻辑,因为有
dataset.loaded这个标记挡着。等确认加载完成后再调用unobserve,确保性能不受影响。另外一个小建议,如果你的图片列表很长,可以考虑在初始化时就给每个图片元素加上
loading="lazy"属性,这是原生支持的懒加载功能,能帮你减少一部分工作量。不过这个功能在某些老旧浏览器上可能不支持,所以还是得结合 Intersection Observer 使用。别走弯路,直接用这个方法试试吧,应该能解决你的问题。