Back

使用 Intersection Observer 检测元素进入视口

使用 Intersection Observer 检测元素进入视口

使用滚动事件监听器跟踪元素可见性会严重影响网站性能。每次滚动都会触发多个事件,每个事件都会调用 getBoundingClientRect() 并强制浏览器进行昂贵的重排操作。Intersection Observer API 优雅地解决了这个问题,为检测元素进入或离开视口提供了原生的浏览器优化。

核心要点

  • Intersection Observer 消除了滚动事件监听器造成的性能瓶颈
  • 该 API 异步运行,防止主线程阻塞
  • 一个观察器可以高效监控多个元素
  • 原生浏览器优化比手动计算提供更好的性能

传统滚动事件的不足之处

滚动事件监听器在滚动过程中持续触发,通常每秒触发 60 多次。每个调用 getBoundingClientRect() 的事件处理器都会强制浏览器重新计算布局,造成卡顿的滚动体验。当多个库独立跟踪可见性时——用于广告、分析和懒加载——性能影响会急剧放大。

Intersection Observer 方法将这些计算移出主线程,让浏览器优化何时以及如何进行交集检查。

理解 Intersection Observer 基础

Intersection Observer API 异步监视目标元素与根元素(通常是视口)边界的交叉。它不是持续轮询,而是仅在跨越可见性阈值时通知您。

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is visible');
    }
  });
});

observer.observe(document.querySelector('.target'));

浏览器内部处理所有交集计算,通过包含 IntersectionObserverEntry 对象的回调传递结果。每个条目提供 isIntersecting(布尔值)和 intersectionRatio(0-1 可见性百分比)。

创建您的第一个观察器

设置 Intersection Observer 只需要一个回调函数和可选配置:

const callback = (entries, observer) => {
  entries.forEach(entry => {
    // 可用的关键属性
    console.log({
      isVisible: entry.isIntersecting,
      visibilityRatio: entry.intersectionRatio,
      targetElement: entry.target
    });
  });
};

const options = {
  root: null,        // 视口
  rootMargin: '0px', // 无偏移
  threshold: 0.5     // 50% 可见
};

const observer = new IntersectionObserver(callback, options);
observer.observe(document.querySelector('.target'));

回调接收一个条目数组,因为观察器可以同时跟踪多个元素。每个条目的 isIntersecting 指示当前可见性,而 intersectionRatio 提供精确的可见性百分比。

配置观察器选项

三个选项控制何时触发交集回调:

root:定义要监视的可滚动区域。null 使用视口;任何可滚动元素都可以作为自定义根。

rootMargin:扩展或收缩根的边界框。使用 CSS 边距语法:"50px""10% 0px"。负值收缩;正值扩展检测区域。

threshold:触发回调的可见性百分比。单个值:0.5(50%)。多个触发器的数组:[0, 0.25, 0.5, 0.75, 1]

高效监视多个元素

一个观察器实例可以监控无限个元素:

const observer = new IntersectionObserver(callback, options);
const targets = document.querySelectorAll('.lazy-load');

targets.forEach(target => observer.observe(target));

// 停止监视特定元素
// observer.unobserve(element);

// 停止监视所有元素
// observer.disconnect();

这种模式最大化效率——浏览器优化单个观察器监视数百个元素比多个观察器效果更好。

实际应用示例

图片懒加载

const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imageObserver.unobserve(img);
    }
  });
}, { rootMargin: '50px' });

document.querySelectorAll('img[data-src]').forEach(img => {
  imageObserver.observe(img);
});

滚动触发动画

const animationObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('animate');
    }
  });
}, { threshold: 0.1 });

document.querySelectorAll('.animate-on-scroll').forEach(element => {
  animationObserver.observe(element);
});

视口内视频自动播放

const videoObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const video = entry.target;
    if (entry.isIntersecting) {
      video.play();
    } else {
      video.pause();
    }
  });
}, { threshold: 0.5 });

document.querySelectorAll('video').forEach(video => {
  videoObserver.observe(video);
});

总结

Intersection Observer API 将视口检测从性能瓶颈转变为优化的浏览器功能。通过用 intersection observer 替换滚动事件监听器,您可以消除主线程阻塞,同时获得更精确的可见性控制。立即开始迁移基于滚动的可见性代码——您用户的浏览器会感谢您。

常见问题

可以,只需在新元素添加到 DOM 后对其调用 observer.observe()。同一个观察器实例可以监控在页面生命周期中任何时间添加的元素。

回调会立即触发,显示元素的当前交集状态。这确保您始终知道初始可见性状态,无需单独检查。

在回调中检查 entry.isIntersecting 是否为 false。观察器会根据您的阈值设置通知您元素进入和退出观察区域的情况。

现代浏览器原生支持。对于 Internet Explorer 等较旧浏览器,使用 W3C 官方 polyfill,它通过 JavaScript 提供相同的功能。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay