登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

IntersectionObserver 怎样按可见性加载内容

来源:17golang原创

时间:2026-10-21 18:10:25 182浏览 收藏

如果页面先把所有图片、推荐卡片或下一页数据都加载进来,首屏会为用户暂时看不到的内容付出请求和渲染成本。IntersectionObserver 可以异步观察目标与视口(或指定滚动容器)的交集,在可见性跨过阈值时调用回调。按可见性加载的关键不是监听每一次滚动,而是把“什么时候算接近”写进观察器配置。

本文只讨论观察阈值、预加载距离和取消观察。打开弹窗、分页接口鉴权等业务逻辑仍由页面自己的交互层负责。

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。

IntersectionObserver 从创建、观察、进入视口到取消观察的生命周期说明图

用 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。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>