滚动到才加载:用 Intersection Observer 给长页面做图片懒加载
电商详情页、图集、长博客,图片一多,首屏就得把几十张图全下完。用户还没滚到下面,光等图片就白屏或转圈半天。更糟的是,那些用户根本没看到的图也占着带宽和流量,移动端尤其浪费。懒加载(lazy loading)就是治这个的:图片先不请求,等它快滚进可视区域了再下。早年的做法是在 scroll 事件里用 getBoundingClientRect 算每个图相对视口的位置,算得勤了卡主线程,算得疏了又不准。现在浏览器给了原生方案 Intersection Observer,专门负责"某个元素进没进视口、交叉了多少",不用我们手算坐标。
一、Intersection Observer 到底干了啥
它像一个"观察者":你告诉它"盯着这几个元素",再给个回调。浏览器会在合适的时机(通常是渲染空闲)主动通知你:“这几个元素现在和视口交叉了,交叉比例是 X”。你完全不用监听 scroll、不用读布局。对比 scroll 方案,它的好处有两点:一是不强制你每次滚动都跑 JS 算位置,省主线程;二是浏览器自己掌握了回调时机,批量处理更高效。回调收到的 entries 是个数组,每项带 isIntersecting(是否交叉)、intersectionRatio(交叉比例)、target(被观察的元素)。
二、最小实现
HTML 里图片先不写 src,改用一个自定义属性 data-src 存真实地址,src 暂时指向一张极小的占位图(纯色或模糊缩略都行):
<img class="lazy" alt="示例">
JS 部分:
const imgs = document.querySelectorAll('img.lazy')
const io = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue
const img = entry.target
img.src = img.dataset.src // 进视口才真正去下载
img.classList.remove('lazy')
io.unobserve(img)
}
})
imgs.forEach((img) => io.observe(img))
逻辑就三行关键:判断 isIntersecting 为真 → 把 data-src 赋给 src 触发下载 → unobserve 取消观察。unobserve 很重要,否则元素一直在被观察,以后每次滚动浏览器都还通知你一次,白耗。占位图要足够小,不然懒加载省下的带宽又被占位图吃回去。
三、提前一点、或可设阈值
默认是"刚碰到视口边缘"才触发,用户快速滚动时可能看到图在转圈才出来。用 rootMargin 把触发区域往外扩 200px,元素还没进视口就提前下:
new IntersectionObserver(cb, { rootMargin: '200px' })
想更细地控制"露出多少才加载",用 threshold 设交叉比例,比如 0.1 表示露出 10% 才触发,传数组能分多档回调:
new IntersectionObserver(cb, { threshold: [0, 0.1, 0.5, 1] })
root 还能指定某个滚动容器(默认是视口),做内部滚动区的懒加载时用得上。
四、一个能直接用的完整片段
把结构和样式合一起,占位用纯色背景,图片加载完淡入:
<style>
img.lazy { width: 100%; background: #eee; min-height: 200px; }
img.lazy:not(.loaded) { opacity: 0; }
img.loaded { opacity: 1; transition: opacity .3s; }
</style>
<img class="lazy" alt="图片示例">
<script>
const io = new IntersectionObserver((es) => {
for (const e of es) {
if (!e.isIntersecting) continue
const img = e.target
img.onload = () => img.classList.add('loaded')
img.src = img.dataset.src
io.unobserve(img)
}
})
document.querySelectorAll('img.lazy').forEach((i) => io.observe(i))
</script>
onload 里加 loaded 类触发淡入,体验更顺。
五、几个要注意的点
第一,给 img 设好宽高或 aspect-ratio,否则加载前后高度变化会让下面的内容猛地跳动,体验很差。第二,首屏以上的图(above the fold)别加 lazy,直接正常加载,避免一打开看到一片占位灰块。第三,data-src 里的地址要可信,别直接拼用户输入,防止被塞进奇怪的协议。第四,老浏览器不支持 IO 时降级成全加载,用 'IntersectionObserver' in window 判断,没有就直接把所有 data-src 赋给 src。第五,原生 <img loading="lazy"> 也能懒加载,但 Intersection Observer 更可控、还能用于"曝光统计""无限滚动"等场景,按需选。
小结
图片懒加载用 Intersection Observer 最省心:data-src 存真地址、进视口才赋值 src、加载完 unobserve。配 rootMargin 提前加载、threshold 控制露出比例,比手算 scroll 位置简单也流畅。占位图要小、首屏图别懒、设好尺寸防跳动,老环境再降级。需要更自动的方案也可以用原生 loading=“lazy”,但 IO 在控制和复用上更强。
- 点赞
- 收藏
- 关注作者
评论(0)