数据预取时使用IntersectionObserver,为什么预加载的图片反而延迟显示?
在开发图片列表页时,我用IntersectionObserver做预加载,但发现预加载的图片比普通加载还慢。代码逻辑没问题,但实际效果反直觉,求大神指点!
我的实现是这样写的:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 触发预加载
observer.unobserve(img);
}
});
}, { rootMargin: '0px', threshold: 1.0 });
document.querySelectorAll('.lazy-img').forEach(img => observer.observe(img));
测试时发现:当用户滚动到触发预加载的位置后,这些图片反而比后续直接加载的图片晚1-2秒显示。明明预加载应该提前获取资源啊?我尝试过把threshold调小到0.5,但问题依旧存在。监控网络请求发现,预加载的图片确实比普通加载早发起请求,但完成时间反而更晚…
具体来说,当用户滚动到图片位置时,浏览器认为这些图片是“高优先级”资源,需要立即渲染。而之前通过预加载发起的请求,浏览器可能把它当作“低优先级”后台任务处理,所以反而变慢了。这种情况在弱网环境或者图片较大的时候尤其明显。
解决办法有几个,我建议你可以试试下面这种实现方式:把预加载的逻辑稍微调整一下,用一个新的Image对象提前加载图片,等加载完成后再赋值给真正的img标签。代码可以改成这样:
这里有几个需要注意的地方:
1. 我把rootMargin调大了一点,比如设置成'200px',让IntersectionObserver在图片进入视口前就提前触发加载,而不是等到完全可见才开始。
2. threshold设成了0.01,这样只要图片有一点点露出就开始加载,避免用户滚动太快导致空白。
3. 使用新的Image对象预加载图片,能确保图片完全加载完成后再赋值给真实DOM节点,防止出现闪烁或者延迟问题。
另外提醒一下,别忘了对data-src属性里的URL做校验,防止注入攻击。如果你是从后端接口拿到的图片地址,最好在服务端也做一层过滤,确保返回的是合法的图片链接。
最后吐槽一句,浏览器的资源调度策略有时候真的让人摸不着头脑,明明预加载应该更快,结果反而被它“聪明”地拖慢了。希望这个方案能帮你解决问题,祝你的项目顺利上线!