一、引言
如果你做过内容型页面(电商列表、图文社区、后台管理、营销落地页),一定遇到过这些问题:
- 页面首屏加载慢
- 图片请求过多
- 用户根本没滚到下面,却已经下载了几十张图片
过去我们常用 scroll + getBoundingClientRect 去判断元素是否进入视口,但这种方式:
- 高频触发
- 容易造成主线程压力
- 代码维护成本高
而 IntersectionObserver API 的出现,本质上就是浏览器帮我们做了"可见性监听",并且是 异步且性能友好 的方式。
二、需求背景
真实业务场景通常长这样:
- 商品列表:一次渲染 100+ 图片
- feed流(小红书发现页、微博首页推荐):无限滚动加载
传统加载方式:
html
<img src="real-image.jpg" />
问题是:
- 页面初始化瞬间请求过多
- 占满带宽影响关键资源
- 首屏指标(LCP / FCP)下降
我们希望做到:
- 图片 进入可视区域附近 才加载
- 加载逻辑对业务无侵入
- 可配置、可复用
三、工作原理(核心理解)
1️⃣ IntersectionObserver API 是什么
简单说:
浏览器帮你监听某个元素是否进入视口。
它通过异步计算元素与 viewport(或父容器)的交集,而不是依赖 scroll 事件轮询。
2️⃣ IntersectionObserver API 三个关键概念
root
观察的根节点:
null→ 浏览器视口- 某个滚动容器 → 局部懒加载
rootMargin(非常重要)
提前触发加载的"预加载区域"。
例如:
js
rootMargin: "100px"
表示图片距离视口还有 100px 时就开始请求。
实战经验:100~300px 是比较平衡的值。
threshold
触发比例。
js
threshold: 0.1
表示元素有 10% 可见时触发。
3️⃣ 为什么比 scroll 更快?
因为:
- 浏览器内部优化
- 批量计算
- 回调异步执行
- 避免频繁 layout
这也是为什么它被广泛用于懒加载、曝光统计、无限滚动。
四、代码实现
下面是一套我在实际项目里常用的实现。
1.HTML 结构
javascript
// 注意:真正的图片地址放在 data-src
<img class="imgs" data-src="https://example.com/a.jpg" alt="" />
2.初始化 Observer
javascript
const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return
const img = entry.target
// 真正加载图片
img.src = img.dataset.src
// 加载完成后取消观察(关键优化)
obs.unobserve(img)
})
},
{
root: null,
rootMargin: "200px 0px",
threshold: 0.01,
}
)
3.开始观察
javascript
document.querySelectorAll(".imgs").forEach((img) => {
observer.observe(img);
});
4.失败兜底 + 占位图
javascript
img.onerror = () => {
img.src = "/images/fallback.png";
};
五、解决了哪些痛点?
✅ 1. 首屏速度明显提升
非首屏图片不加载,减少初始网络请求。
✅ 2. JS 开销下降
不用自己写:
javascript
window.addEventListener("scroll", handler);
避免频繁计算。
✅ 3. 自动资源释放
使用:
javascript
observer.unobserve(img);
监听一次即销毁,避免内存累积。
✅ 4. 代码可维护
- 可封装成 hook / directive
- Vue / React 都能直接复用
六、竞品方案分析
方案 1:scroll + throttle
优点
- 兼容极老浏览器
缺点
- 高频触发
- 需要手动计算
- 易产生性能问题
方案 2:原生 loading="lazy"
html
<img loading="lazy" src="a.jpg" />
现代浏览器已经支持。
优点
- 一行代码
- 几乎零成本
缺点
- 无法精准控制
- 自定义行为有限
- 某些复杂场景不足
方案 3:IntersectionObserver(推荐)
优势
- 可控性强
- 性能优秀
- 支持预加载区
- 可扩展为埋点 / 曝光统计
兼容率已接近 96%(现代浏览器基本无压力)。
⭐ Observer 尽量复用
不要每个图片 new 一个。
⭐ 搭配 skeleton 更丝滑
体验远比 loading spinner 好。
竞品性能分析
| 方案 | 触发机制 | 主线程压力 | Scroll 流畅度 | 复杂度 | 典型问题 | 适合场景 |
|---|---|---|---|---|---|---|
| IntersectionObserver | 浏览器异步计算可见性 | ⭐ 低 | ⭐⭐⭐⭐⭐ | 中 | 老浏览器需 polyfill | 图片懒加载、Feed 流、曝光统计 |
| scroll + getBoundingClientRect | scroll 高频事件 | ❗ 高 | ⭐⭐ | 中 | 高频计算导致卡顿 | 小型页面 / 简单需求 |
| requestAnimationFrame 轮询 | 每帧执行检测 | ❗ 高 | ⭐⭐ | 高 | 与渲染帧竞争资源 | 动画相关场景 |
原生 loading="lazy" |
浏览器内置策略 | ⭐ 极低 | ⭐⭐⭐⭐⭐ | ⭐ 最低 | 控制能力有限 | 简单图片懒加载 |
九、总结
一句话总结:
IntersectionObserver API 把"监听滚动"这件事交回给浏览器,是真正工程级的懒加载方案。
它不仅适用于图片,还可以扩展到:
- 无限滚动
- 曝光埋点
- 视频自动播放
- 动画触发
作者: 王新焱
博客: https://blog.csdn.net/qq_34402069
时间: 2026年2月14日