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

相关推荐
snow@li11 小时前
前端:前端/浏览器 可以录屏吗 / 实践 / 录制 Microsoft Edge 标签页、应用窗口、整个屏幕
前端·浏览器录屏·前端录屏·web录屏
李贺梖梖11 小时前
CSS学习
前端·css
蚂小蚁11 小时前
一文吃透:宏任务、微任务、事件循环、浏览器渲染、Vue 批处理与 Node 差异(含性能优化)
前端·面试·架构
狼性书生12 小时前
uniapp实现的Tab 选项卡组件模板
前端·uni-app·vue·组件·插件
吃饺子不吃馅12 小时前
前端画布类型编辑器项目,历史记录技术方案调研
前端·架构·github
拜晨12 小时前
使用motion实现小宇宙贴纸墙效果
前端·交互设计
拜晨12 小时前
使用motion实现小宇宙节目广场的效果
前端·交互设计
知花实央l12 小时前
【Web应用实战】 文件上传漏洞实战:Low/Medium/High三级绕过(一句话木马拿webshell全流程)
前端·学习·网络安全·安全架构
华仔啊12 小时前
JavaScript + Web Audio API 打造炫酷音乐可视化效果,让你的网页跟随音乐跳起来
前端·javascript