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 事件收起来吧!

相关推荐
行思理2 分钟前
Linux查看网站访问IP的命令大全
linux·服务器·前端
晓13133 分钟前
第四章 TypeScript 类型声明文件与 React 运用
前端·react.js·typescript
大雷神9 分钟前
HarmonyOS APP<玩转React>开源教程二十一:测验服务层实现
前端·react.js·开源·harmonyos
apcipot_rain10 分钟前
事无巨细地解释一个vue前端网页
前端·javascript·vue.js
han_12 分钟前
JavaScript设计模式(三):代理模式实现与应用
前端·javascript·设计模式
~欲买桂花同载酒~17 分钟前
项目安装- React + TypeScript
前端·react.js·typescript
光辉GuangHui17 分钟前
SDD 实践:OpenSpec + Superpowers 整合创建自定义工作流
前端·后端
ssshooter24 分钟前
infer,TS 类型系统的手术刀
前端·面试·typescript
用户31673613034225 分钟前
图片懒加载,我总结了三个方式
前端
灰太狼大大王26 分钟前
2026 前端基石:HTML5 全景知识体系指南(从入门到架构师思维)
前端