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

相关推荐
MediaTea8 小时前
思考与练习(第四章 程序组成与输入输出)
java·linux·服务器·前端·javascript
BD_Marathon8 小时前
【JavaWeb】NPM_简介和相关配置
前端·npm·node.js
咸鱼加辣8 小时前
【前端框架】react
前端·react.js·前端框架
unicrom_深圳市由你创科技8 小时前
Vue 3 高效开发技巧总结
前端·javascript·vue.js
HIT_Weston8 小时前
66、【Ubuntu】【Gitlab】拉出内网 Web 服务:Gitlab 配置审视(十)
前端·ubuntu·gitlab
长空任鸟飞_阿康8 小时前
LangChain 技术栈全解析:从模型编排到 RAG 实战
前端·python·langchain
chilavert3188 小时前
技术演进中的开发沉思-258 Ajax:自定义事件
前端·ajax·okhttp
南知意-8 小时前
从零搭建 Live2D 看板娘教程(自建API避墙版)
服务器·前端·vue.js·开源·博客·美化·看板娘
来杯三花豆奶8 小时前
Vue 2 中 Store (Vuex) 从入门到精通
前端·javascript·vue.js
Lethehong8 小时前
React构建实时股票分析系统:蓝耘MaaS平台与DeepSeek-V3.2的集成实践
前端·react.js·前端框架·蓝耘mcp·蓝耘元生代·蓝耘maas