IntersectionObserver 异步交叉观察器

IntersectionObserver 是浏览器原生提供的「异步交叉观察器」,用来高效监听「目标元素与其祖先或视口是否相交」以及「相交比例变化」。相比传统的 scroll + getBoundingClientRect 方案,它把计算工作下沉到浏览器内核,不阻塞主线程、无需手动节流、精度高、代码少,任何需要「元素可见性」判断的场景都能用它。


一、核心 API(记忆 3 个步骤)

  1. 新建观察者
    const io = new IntersectionObserver(callback, options)
  2. 告诉它要观察谁
    io.observe(DOM元素) 可多次调用,一个实例可同时观察 N 个节点
  3. 用完收回
    io.unobserve(元素)io.disconnect()

callback 会被传入两个形参:
entries -- 本次发生变化的 IntersectionObserverEntry 数组
observer -- 当前观察者实例,可用于 unobserve

entry 上最常用的 3 个字段

  • isIntersecting 布尔值 -- 是否「可见」
  • intersectionRatio 0-1 -- 可见比例
  • target -- 被观察的 DOM 节点

options 3 选 1 均可省

  • root 默认 null(= 视口),也可指定祖先滚动容器
  • rootMargin 扩大/缩小触发范围,写法同 CSS margin("50px 0")
  • threshold 触发阈值,默认 0(刚碰到就触发),可写数组 [0, 0.5, 1]

二、4 个常见真实场景与可直接粘贴的示例

  1. 图片懒加载(最经典)
html 复制代码
<img class="lazy" src="placeholder.png" data-src="real.jpg" width="400">
<img class="lazy" src="placeholder.png" data-src="real2.jpg" width="400">
<script>
const io = new IntersectionObserver((entries, ob) => {
  entries.forEach(en => {
    if (en.isIntersecting) {               // 进入视口
      const img = en.target;
      img.src = img.dataset.src;           // 换真实地址
      ob.unobserve(img);                   // 立即取消监听
    }
  });
}, { rootMargin: '100px' });               // 提前 100px 开始加载

document.querySelectorAll('img.lazy').forEach(el => io.observe(el));
</script>
  1. 无限滚动(触底加载下一页)
html 复制代码
<ul id="list"></ul>
<li id="sentinel">加载中...</li>
<script>
let page = 1;
const io = new IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) {              // 底部元素完全可见
    loadMore();                            // 请求接口
  }
}, { threshold: 1 });

function loadMore() {
  fetch(`/api/list?page=${page++}`)
    .then(r => r.json())
    .then(arr => {
      const ul = document.getElementById('list');
      arr.forEach(d => ul.insertAdjacentHTML('beforeend', `<li>${d}</li>`));
    });
}
io.observe(document.getElementById('sentinel'));
</script>
  1. 元素出现即播放动画(一次性)
css 复制代码
.fadeIn { opacity: 0; transform: translateY(20px); transition: .6s }
.fadeIn.show { opacity: 1; transform: translateY(0) }
html 复制代码
<div class="fadeIn">Hello</div>
<div class="fadeIn">World</div>
<script>
const io = new IntersectionObserver((entries, ob) => {
  entries.forEach(en => {
    if (en.isIntersecting) {
      en.target.classList.add('show');
      ob.unobserve(en.target);             // 动画只需一次
    }
  });
}, { threshold: .3 });                    // 30% 可见就触发
document.querySelectorAll('.fadeIn').forEach(el => io.observe(el));
</script>
  1. 广告/组件「曝光埋点」
javascript 复制代码
const io = new IntersectionObserver((entries, ob) => {
  entries.forEach(en => {
    if (en.isIntersecting && en.intersectionRatio >= 1) {
      // 整卡完全可见,只发一次
      gtag('event', 'ad_impression', { element: en.target.dataset.id });
      ob.unobserve(en.target);
    }
  });
}, { threshold: 1 });

document.querySelectorAll('.ad-card').forEach(card => io.observe(card));

三、易踩小坑 & 性能提示

  • 观察器实例可以复用,不要给每个元素 new 一个
  • 回调里不要做重计算/同步 IO,必要时用 requestIdleCallback 延后
  • 节点移除后记得 unobserve,SPA 切换页时记得 disconnect() 防止泄漏
  • 旧版浏览器(IE)需加载 polyfill(github.com/w3c/IntersectionObserver)
  • 若滚动容器不是 window,记得把 root 指向那个「可滚动祖先」

四、一句话总结

IntersectionObserver = 「可见性变化」专属黑科技:
代码更少、性能更好、功能刚好,懒加载、无限滚、动画、埋点全能打。今天开始,把 scroll 事件收起来吧!

相关推荐
接着奏乐接着舞。6 分钟前
部署BFF与前端的踩坑与经验记录
前端·node.js
小李子呢02117 小时前
前端八股CSS(2)---动画的实现方式
前端·javascript
GreenTea8 小时前
从 Claw-Code 看 AI 驱动的大型项目开发:2 人 + 10 个自治 Agent 如何产出 48K 行 Rust 代码
前端·人工智能·后端
渣渣xiong9 小时前
从零开始:前端转型AI agent直到就业第五天-第十一天
前端·人工智能
布局呆星9 小时前
Vue3 | 组件通信学习小结
前端·vue.js
C澒9 小时前
IntelliPro 企业级产研协作平台:前端智能生产模块设计与落地
前端·ai编程
OpenTiny社区11 小时前
重磅预告|OpenTiny 亮相 QCon 北京,共话生成式 UI 最新技术思考
前端·开源·ai编程
前端老实人灬11 小时前
web前端面试题
前端
Moment11 小时前
AI 全栈指南:NestJs 中的 Service Provider 和 Module
前端·后端·面试