干了10年前端,才学会使用IntersectionObserver

IntersectionObserver 是 JavaScript 原生 API,用于异步监听目标元素与视口(或指定容器元素)的交叉状态(即元素是否进入 / 离开视口、交叉比例多少)。核心优势是性能优异(浏览器原生优化,避免 scroll 事件的高频触发),常见场景:懒加载图片 / 视频、滚动加载列表、曝光统计、元素进入视口时触发动画等。

一、核心概念

  1. 目标元素(target) :需要监听的 DOM 元素(如图片、列表项)。
  2. 根元素(root) :作为 "视口" 的参考容器,默认是浏览器视口(null),必须是目标元素的祖先元素。
  3. 根边界(rootMargin) :根元素的 "扩展 / 收缩边距",用于提前 / 延迟触发监听(如提前 100px 检测元素即将进入视口)。
  4. 阈值(threshold) :触发回调的 "交叉比例阈值"(0~1),可传单个值或数组(如 [0, 0.5, 1] 表示元素刚进入、一半进入、完全进入时都触发)。
  5. 交叉状态(intersectionRatio) :目标元素与根元素的交叉比例(0 = 完全不交叉,1 = 完全交叉)。

二、基本使用步骤

1. 语法结构

javascript 复制代码
// 1. 创建观察器实例,传入回调函数和配置项
const observer = new IntersectionObserver((entries, observer) => {
  // entries:所有被监听元素的交叉状态数组(每个元素是 IntersectionObserverEntry 对象)
  entries.forEach(entry => {
    // entry:单个元素的交叉状态信息
    if (entry.isIntersecting) {
      // 元素进入视口(交叉比例 > 0)
      console.log('元素进入视口', entry.target);
      // 执行业务逻辑(如懒加载、触发动画)
      // 可选:只监听一次,触发后取消观察
      observer.unobserve(entry.target);
    } else {
      // 元素离开视口(交叉比例 = 0)
      console.log('元素离开视口', entry.target);
    }
  });
}, {
  root: null, // 根元素,默认视口(null)
  rootMargin: '0px', // 根元素边距(格式:上 右 下 左,支持 px/%)
  threshold: 0 // 阈值(默认 0,元素刚进入视口时触发)
});

// 2. 监听目标元素(可监听多个)
const target1 = document.querySelector('.target1');
const target2 = document.querySelector('.target2');
observer.observe(target1);
observer.observe(target2);

// 3. 可选:停止监听单个元素
observer.unobserve(target1);

// 4. 可选:销毁观察器(所有监听都停止)
observer.disconnect();

2. 关键参数详解

参数 说明
entries 数组,每个元素是 IntersectionObserverEntry 对象,包含单个目标的交叉信息:- isIntersecting:布尔值,是否正在交叉(进入视口)- intersectionRatio:交叉比例(0~1)- target:被监听的 DOM 元素- boundingClientRect:目标元素的位置信息- rootBounds:根元素的位置信息
root 参考容器(DOM 元素),默认 null(浏览器视口),必须是目标元素的祖先。
rootMargin 根元素的边距,用于扩展 / 收缩根元素的 "有效视口",格式同 CSS 边距。例:rootMargin: '50px 0px' → 根元素上下扩展 50px,提前 50px 触发监听。
threshold 触发回调的交叉比例阈值,可传数组:- threshold: 0(默认)→ 元素刚进入视口(交叉比例 >0)时触发- threshold: 1 → 元素完全进入视口(交叉比例 =1)时触发- threshold: [0, 0.5, 1] → 元素刚进入、一半进入、完全进入时各触发一次

三、常见场景示例

示例 1:图片懒加载(核心场景)

需求:页面滚动时,图片进入视口后再加载真实图片(优化首屏加载速度)。

html 复制代码
<!-- HTML:占位图 + 真实图片地址存放在 data-src 属性 -->
<img class="lazy-img" src="placeholder.jpg" data-src="real-img1.jpg" alt="懒加载图片">
<img class="lazy-img" src="placeholder.jpg" data-src="real-img2.jpg" alt="懒加载图片">
javascript 复制代码
// 1. 创建观察器
const lazyLoadObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 元素进入视口,加载真实图片
      const img = entry.target;
      img.src = img.dataset.src; // 替换 src 为真实地址
      img.classList.add('loaded'); // 可选:添加加载完成样式
      lazyLoadObserver.unobserve(img); // 只加载一次,取消监听
    }
  });
}, {
  rootMargin: '100px 0px', // 提前 100px 开始加载(优化体验,避免白屏)
  threshold: 0.1 // 元素 10% 进入视口时触发
});

// 2. 监听所有懒加载图片
document.querySelectorAll('.lazy-img').forEach(img => {
  lazyLoadObserver.observe(img);
});

示例 2:滚动加载更多(无限滚动)

需求:滚动到页面底部的 "加载更多" 按钮时,请求下一页数据。

html 复制代码
<ul class="list"></ul>
<div class="load-more">加载更多</div>
javascript 复制代码
const loadMoreBtn = document.querySelector('.load-more');
const list = document.querySelector('.list');
let page = 1;

// 创建观察器(监听"加载更多"按钮)
const loadMoreObserver = new IntersectionObserver((entries) => {
  const [entry] = entries;
  if (entry.isIntersecting && !isLoading) { // isLoading 防止重复请求
    isLoading = true;
    loadMoreBtn.textContent = '加载中...';
    // 模拟请求下一页数据
    fetch(`/api/data?page=${page}`)
      .then(res => res.json())
      .then(data => {
        data.forEach(item => {
          const li = document.createElement('li');
          li.textContent = item.content;
          list.appendChild(li);
        });
        page++;
        isLoading = false;
        loadMoreBtn.textContent = '加载更多';
      });
  }
}, { rootMargin: '50px 0px' }); // 提前 50px 触发,优化体验

// 监听"加载更多"按钮
loadMoreObserver.observe(loadMoreBtn);

示例 3:元素进入视口触发动画

需求:元素滚动进入视口时,添加 "淡入" 动画。

css 复制代码
/* CSS:初始状态(透明、偏移)+ 动画状态 */
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.5s, transform 0.5s;
}
.fade-in.active {
  opacity: 1;
  transform: translateY(0);
}
html 复制代码
<div class="fade-in">元素 1:进入视口淡入</div>
<div class="fade-in">元素 2:进入视口淡入</div>
javascript 复制代码
// 创建观察器
const animationObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('active'); // 触发动画
      animationObserver.unobserve(entry.target); // 只触发一次
    }
  });
}, { threshold: 0.3 }); // 元素 30% 进入视口时触发

// 监听所有需要动画的元素
document.querySelectorAll('.fade-in').forEach(el => {
  animationObserver.observe(el);
});

四、注意事项

  1. 兼容性 :支持 Chrome 51+、Firefox 55+、Safari 12.1+、Edge 16+,不支持 IE(需兼容可使用 polyfill)。
  2. 异步特性IntersectionObserver 是异步的,回调函数不会阻塞主线程(性能优势),但无法同步获取元素交叉状态。
  3. root 必须是祖先元素 :如果指定 root,必须确保它是目标元素的父级 / 祖先元素,否则监听无效。
  4. 动态元素监听 :如果目标元素是动态创建的(如通过 JS 新增的列表项),创建后需调用 observer.observe(新元素) 手动添加监听。
  5. rootMargin 单位 :支持 px%,但不能混合单位(如 50px 10% 是允许的,50 10px 不允许)。

五、总结

IntersectionObserver 的核心价值是高效监听元素交叉状态 ,替代传统的 scroll + getBoundingClientRect() 方案(后者高频触发,性能较差)。使用时只需三步:

  1. 创建观察器(配置回调和参数);
  2. 监听目标元素;
  3. 触发后执行业务逻辑(并可选取消监听)。

常见应用:懒加载、无限滚动、曝光统计、滚动动画,是前端性能优化和交互体验提升的重要 API。

相关推荐
S***y3962 小时前
前端微前端框架对比,qiankun与icestark
前端·前端框架
Wect2 小时前
学习React-DnD:实现多任务项拖拽-useDrop处理
前端·react.js
Mintopia2 小时前
Trae Coding - 「Excel 秒变海报」—— 上传 CSV,一句话生成可打印信息图。
前端·人工智能·trae
晴殇i2 小时前
CSS 相对颜色:告别 180 个颜色变量的设计系统噩梦
前端·css
MegatronKing2 小时前
Reqable 3.0版本云同步的实践过程
前端·后端·测试
李剑一2 小时前
我用Trae生成了一个Echarts 3D柱状图的Demo
前端·vue.js·trae
Crystal3282 小时前
3D实战案例(飞行的火箭/创建3D导航/翻书效果/创建长方体/环环相扣效果)
前端·css
6***x5452 小时前
前端组件库发展趋势,原子化CSS会成为主流吗
前端·css
mine_mine2 小时前
油猴脚本拦截fetch和xhr请求,实现修改服务端接口功能
javascript