IntersectionObserver 怎样按可见性加载内容
来源:17golang原创
时间:2026-10-21 18:10:25 182浏览 收藏
如果页面先把所有图片、推荐卡片或下一页数据都加载进来,首屏会为用户暂时看不到的内容付出请求和渲染成本。IntersectionObserver 可以异步观察目标与视口(或指定滚动容器)的交集,在可见性跨过阈值时调用回调。按可见性加载的关键不是监听每一次滚动,而是把“什么时候算接近”写进观察器配置。
本文只讨论观察阈值、预加载距离和取消观察。打开弹窗、分页接口鉴权等业务逻辑仍由页面自己的交互层负责。

先确定什么叫“进入可见区域”
构造器的第二个参数决定观察边界。root: null 表示使用顶层文档视口;如果目标在一个独立滚动容器中,则把该容器传给 root。rootMargin 会扩大或收缩用于计算的根区域,适合让图片在真正进入屏幕前提前请求。threshold 是交集比例阈值,可以是一个数字,也可以是数组。
const observerOptions = {
// null 表示使用浏览器视口;滚动容器场景应传入容器元素。
root: null,
// 提前 240px 触发加载,避免用户滚到图片时才开始请求。
rootMargin: "240px 0px",
// 只要目标与观察区域有交集,就触发一次回调。
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
// 回调可能同时收到多个目标,只处理已经进入观察区域的目标。
for (const entry of entries) {
if (!entry.isIntersecting) continue;
console.log("目标接近视口", entry.target);
}
}, observerOptions);
默认阈值是 0,常用于“刚刚碰到观察区域就开始加载”。如果需要在内容至少显示一半时才执行曝光统计,可以使用 threshold: 0.5;如果需要观察多个阶段,则写成 [0, 0.25, 0.5, 1]。观察器创建后配置不能直接修改,想换阈值需要重新创建 observer。
用 data-src 把加载动作放进回调
一个 observer 可以观察多个目标,不需要每张图片都创建一个实例。将真实地址放在 data-src 中,等目标进入区域后再复制到 src,并在处理成功后取消这个目标的观察。
const lazyObserver = new IntersectionObserver((entries, currentObserver) => {
for (const entry of entries) {
// isIntersecting 为 false 时只代表目标离开,不重复发起加载。
if (!entry.isIntersecting) continue;
const image = entry.target;
const source = image.dataset.src;
if (!source) {
// 没有待加载地址时停止观察,避免无意义的回调。
currentObserver.unobserve(image);
continue;
}
// 把真实地址交给浏览器,加载完成后该目标无需继续观察。
image.src = source;
image.removeAttribute("data-src");
currentObserver.unobserve(image);
}
}, {
// 让图片提前进入加载窗口,数值要结合图片大小与网络延迟调整。
rootMargin: "240px 0px",
threshold: 0
});
document.querySelectorAll(".lazy-image").forEach((image) => {
// 使用同一个观察器管理所有懒加载图片。
lazyObserver.observe(image);
});
这里的 unobserve(image) 只移除当前图片,不会影响其他目标。若图片加载失败仍要显示错误占位,可以在 image.onerror 中记录状态,再决定是否保留 data-src;不要因为网络失败就无条件重复设置 src。
threshold 和 rootMargin 不是一回事
threshold 描述“目标有多少比例与根区域相交时通知我”,而 rootMargin 描述“计算相交时把根区域扩大或收缩多少”。懒加载通常把 threshold 保持为 0,通过 rootMargin 给网络请求留提前量;曝光统计则更适合使用较高 threshold,避免只露出一条边就算作看见。
| 需求 | 建议配置 | 触发含义 |
|---|---|---|
| 图片提前加载 | threshold: 0、rootMargin: "240px 0px" | 进入扩大后的观察区域就加载 |
| 卡片显示一半再统计 | threshold: 0.5 | 交集比例跨过 50% 时通知 |
| 滚动容器内加载 | root: scrollPanel | 以容器的可视区域而不是窗口判断 |
需要注意,IntersectionObserver 适合回答“是否达到某个比例”,不适合回答精确重叠了多少像素,也不保证回调在滚动事件的同一时刻同步执行。加载逻辑应当具备幂等性,不要假设回调只会收到一个 entry。

用 unobserve 和 disconnect 收好生命周期
单个目标完成加载后调用 unobserve(target),可以让 observer 继续服务其他图片。组件整体卸载时调用 disconnect(),一次停止它正在观察的全部目标。后者适合路由切换或组件销毁,不适合每加载一张图片就调用。
function createLazyLoader(container) {
const targets = container.querySelectorAll("[data-src]");
const loader = new IntersectionObserver((entries, currentObserver) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const target = entry.target;
// 只消费一次数据地址,避免同一目标重复请求。
target.src = target.dataset.src;
delete target.dataset.src;
currentObserver.unobserve(target);
}
}, { rootMargin: "200px 0px", threshold: 0 });
targets.forEach((target) => loader.observe(target));
return () => {
// 组件卸载时统一释放所有观察关系。
loader.disconnect();
};
}
const stopLazyLoading = createLazyLoader(document.querySelector(".article"));
// 路由或组件销毁时调用 stopLazyLoading()。
如果一个目标在首屏内,观察器通常会很快给出初始通知;不要把“已经在视口内”当成异常。对于动态追加的卡片,只需要在追加后对新节点调用同一个 observer 的 observe(),不必重新观察整个列表。
上线前记住四个边界
- 先决定根是视口还是滚动容器;目标必须处在指定 root 的可观察关系中。
- 懒加载优先用小 threshold 配合适度 rootMargin,提前量要按资源体积和网络延迟调整。
- 回调遍历全部 entries,加载成功后对单个目标调用
unobserve();组件销毁时再调用disconnect()。 - 对不支持 IntersectionObserver 的环境保留简单降级:直接使用真实
src,或在能力检测后加载兼容方案。
if ("IntersectionObserver" in window) {
// 支持时启用按可见性加载。
startLazyLoading();
} else {
// 不支持时直接显示内容,保证功能优先于性能优化。
document.querySelectorAll("[data-src]").forEach((image) => {
image.src = image.dataset.src;
});
}
常见问题
为什么设置 threshold: 0 后没有立刻加载
threshold 只描述交集比例,不负责扩大观察范围。检查目标是否已经调用 observe()、root 是否正确,以及目标是否被隐藏;如果希望提前加载,增加 rootMargin。
目标离开视口后会再次请求吗
如果仍在观察且回调没有设置幂等条件,目标再次进入时可能再次执行逻辑。把地址放在 data-src、成功后删除它,并在完成处理后 unobserve,可以避免重复请求。
什么时候用 disconnect 而不是 unobserve
只想停止一个目标时用 unobserve;整个列表或组件不再存在时用 disconnect。两者都不会替你取消已经发出的网络请求,网络请求的取消需要由 Fetch 或资源加载逻辑单独处理。
参考资料:https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API、https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver。
-
文章 · 前端 | 1小时前 | 前端 · JSON · javascript · JavaScript vite json模块 ES module import attributes with type=json406 收藏
-
文章 · 前端 | 1小时前 | 前端 · css · 浏览器兼容 · anchor-name position-area CSS anchor positioning position-anchor position-try 下拉菜单定位161 收藏
-
252 收藏
-
208 收藏
-
284 收藏
-
432 收藏
-
432 收藏
-
464 收藏
-
379 收藏
-
295 收藏
-
182 收藏
-
150 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习

