节流对象是“可视区检测函数”而非图片加载动作本身,核心是避免高频scroll拖垮主线程;推荐优先使用IntersectionObserver,兼容场景再用throttle包裹getBoundingClientRect检测。

滚动加载图片时用节流控制请求频次,核心不是“拦住所有请求”,而是让判断图片是否进入可视区的逻辑不被高频 scroll 事件拖垮主线程,从而稳定触发懒加载逻辑、避免重复或遗漏。
滚动监听本身必须节流,但图片加载动作通常只在判定后执行一次
图片懒加载的关键环节是:监听滚动 → 检查哪些 <img> 进入视口 → 给符合条件的 img 设置 src。其中,“检查是否进入视口”这个操作(比如调用 getBoundingClientRect() 或 IntersectionObserver 回调)如果每次 scroll 都执行,极易造成卡顿。节流的作用就是把它压到合理频率,比如每 100ms 最多检查一次。
- 节流对象是“可视区检测函数”,不是
img.src = xxx本身 - 一旦某张图被检测到并完成加载,它就不再参与后续检测(可移除监听或打标记)
- 不建议对
fetch或img.onload做节流——它们本就是单次异步动作,节流反而会延迟加载
推荐两种落地方式
-
使用
IntersectionObserver(现代首选)
它原生支持异步、被动监听,无需手动节流。浏览器自动批处理、性能极好,且能精准响应元素进出视口。只需配置rootMargin和threshold,监听回调里直接加载即可。const io = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; io.unobserve(img); // 加载完就取消观察 } }); }, { threshold: 0.1 }); document.querySelectorAll('img[data-src]').forEach(img => io.observe(img)); -
手动节流 +
getBoundingClientRect(兼容旧环境)
若需支持不支持IntersectionObserver的老浏览器,才需自己节流滚动检测逻辑:- 用带
leading: true, trailing: true的节流函数包裹检测逻辑 - 每次触发只遍历尚未加载的图片,避免重复查询
- 检测前加
document.visibilityState === 'visible'判断页面是否可见,防止后台标签页白忙
- 用带
const checkVisible = throttle(() => {
const imgs = document.querySelectorAll('img[data-src]:not([src])');
imgs.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top 0) {
img.src = img.dataset.src;
}
});
}, 100, { leading: true, trailing: true });
window.addEventListener('scroll', checkVisible);
关键细节不能漏
- 节流延迟设为 80–160ms 较稳妥:太小(如 16ms)接近帧率,可能仍抢资源;太大(如 500ms)会导致用户已滚过图片却没加载
- 必须配合
passive: true注册 scroll 监听器,否则浏览器会强制同步等待 JS 执行,直接卡死滚动window.addEventListener('scroll', checkVisible, { passive: true }); - 页面卸载前记得清理:
window.removeEventListener('scroll', checkVisible),尤其 SPA 中易内存泄漏 - 首屏图片建议不用懒加载,直接写
src,避免白屏;懒加载仅用于“非首屏”区域的图片
不复杂但容易忽略











