IntersectionObserver

异步检测元素是否进入视口 - 现代高效的可见性检测 API

API 说明

IntersectionObserver 提供了一种异步检测目标元素与祖先元素或视口相交情况的方法。相比传统的 scroll 事件监听,它具有更好的性能。

const observer = new IntersectionObserver(callback, options);

// 回调函数
function callback(entries, observer) {
  entries.forEach(entry => {
    console.log('元素可见性:', entry.isIntersecting);
    console.log('相交比例:', entry.intersectionRatio);
    console.log('相交区域:', entry.intersectionRect);
  });
}

// 配置选项
const options = {
  root: null,              // 视口
  rootMargin: '0px',       // 根边距
  threshold: 0             // 触发阈值(0-1)
};

// 开始观察
observer.observe(targetElement);
  • isIntersecting - 元素是否与根元素相交(是否可见)
  • intersectionRatio - 相交比例(0 到 1)
  • intersectionRect - 相交区域的矩形信息
  • root - 用作视口的元素,默认为浏览器视口
  • rootMargin - 根元素的边距,可以扩大或缩小触发区域
  • threshold - 触发回调的相交比例阈值

💡 性能优势

IntersectionObserver 使用浏览器内部优化,不会频繁触发主线程。相比 scroll + getBoundingClientRect 的组合,性能大幅提升。

示例 1:基础用法 - 检测元素是否可见

滚动页面,观察元素进入/离开视口时的状态变化

↓ 向下滚动查看目标元素 ↓

目标元素

可见性状态

isIntersecting: -
相交比例: 0%
触发次数: 0
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('元素进入视口');
    } else {
      console.log('元素离开视口');
    }
  });
});

observer.observe(document.getElementById('box'));

示例 2:自定义阈值(threshold)

设置多个触发阈值,精确控制触发时机

↓ 向下滚动 ↓

目标元素

继续滚动...

阈值触发记录

当前比例: 0%
触发阈值: -
触发历史: -
const options = {
  threshold: [0, 0.25, 0.5, 0.75, 1.0]  // 多个阈值
};

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    console.log(`可见比例: ${Math.round(entry.intersectionRatio * 100)}%`);
  });
}, options);

示例 3:rootMargin - 提前触发

使用 rootMargin 提前或延迟触发回调,实现图片预加载等场景

↓ 向下滚动(元素进入前 100px 就会触发)↓

目标元素

继续滚动...

提前触发检测

状态: 等待中
到视口距离: 0px
rootMargin: 100px

💡 应用场景

rootMargin: '100px' 表示元素距离视口还有 100px 时就会触发回调。这对于图片懒加载非常有用,可以在用户看到图片之前就开始加载。

示例 4:无限滚动

检测到底部元素时自动加载更多内容

内容项 1

内容项 2

内容项 3

加载中...

加载状态

加载指示器状态: 检测中
已加载项数: 3
const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    // 加载更多内容
    loadMoreContent();
  }
}, {
  rootMargin: '100px'  // 提前 100px 触发
});

observer.observe(document.getElementById('loading'));

示例 5:懒加载图片

图片进入视口时才加载 src

↓ 向下滚动查看图片加载效果 ↓

图片 1 - 等待加载
图片 2 - 等待加载
图片 3 - 等待加载

继续滚动...

加载统计

已加载图片: 0 / 3
const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      const src = img.getAttribute('data-src');

      // 加载图片
      img.src = src;
      img.classList.add('loaded');

      // 停止观察已加载的图片
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('.lazy-image').forEach(img => {
  imageObserver.observe(img);
});

示例 6:视差动画

根据元素在视口中的位置实现平滑的动画效果

视差元素

动画状态

可见比例: 0%
缩放比例: 1.0
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const ratio = entry.intersectionRatio;

    // 根据可见比例调整缩放
    const scale = 0.5 + (ratio * 0.5);
    entry.target.style.transform = `scale(${scale})`;
  });
}, {
  threshold: Array.from({length: 100}, (_, i) => i / 100)  // 0% 到 100% 每隔 1%
});

浏览器兼容性

API Chrome Firefox Safari Edge
IntersectionObserver ✓ 51+ ✓ 55+ ✓ 12.1+ ✓ 15+
IntersectionObserver v2 ✓ 74+ 部分支持 ✓ 79+

💡 Polyfill

对于不支持 IntersectionObserver 的浏览器,可以使用 W3C polyfill

总结

  • 高性能:不会频繁触发回调,性能优于 scroll 事件
  • 异步:不阻塞主线程,用户体验更好
  • 灵活配置:支持 threshold 和 rootMargin 精确控制触发时机
  • 常用场景:图片懒加载、无限滚动、动画触发、广告曝光统计等
  • 注意事项:回调中避免复杂计算,观察后记得在适当时机 unobserve