IntersectionObserver 是 JavaScript 原生 API,用于异步监听目标元素与视口(或指定容器元素)的交叉状态(即元素是否进入 / 离开视口、交叉比例多少)。核心优势是性能优异(浏览器原生优化,避免 scroll 事件的高频触发),常见场景:懒加载图片 / 视频、滚动加载列表、曝光统计、元素进入视口时触发动画等。
一、核心概念
- 目标元素(target) :需要监听的 DOM 元素(如图片、列表项)。
- 根元素(root) :作为 "视口" 的参考容器,默认是浏览器视口(
null),必须是目标元素的祖先元素。 - 根边界(rootMargin) :根元素的 "扩展 / 收缩边距",用于提前 / 延迟触发监听(如提前 100px 检测元素即将进入视口)。
- 阈值(threshold) :触发回调的 "交叉比例阈值"(0~1),可传单个值或数组(如
[0, 0.5, 1]表示元素刚进入、一半进入、完全进入时都触发)。 - 交叉状态(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);
});
四、注意事项
- 兼容性 :支持 Chrome 51+、Firefox 55+、Safari 12.1+、Edge 16+,不支持 IE(需兼容可使用 polyfill)。
- 异步特性 :
IntersectionObserver是异步的,回调函数不会阻塞主线程(性能优势),但无法同步获取元素交叉状态。 - root 必须是祖先元素 :如果指定
root,必须确保它是目标元素的父级 / 祖先元素,否则监听无效。 - 动态元素监听 :如果目标元素是动态创建的(如通过 JS 新增的列表项),创建后需调用
observer.observe(新元素)手动添加监听。 - rootMargin 单位 :支持
px或%,但不能混合单位(如50px 10%是允许的,50 10px不允许)。
五、总结
IntersectionObserver 的核心价值是高效监听元素交叉状态 ,替代传统的 scroll + getBoundingClientRect() 方案(后者高频触发,性能较差)。使用时只需三步:
- 创建观察器(配置回调和参数);
- 监听目标元素;
- 触发后执行业务逻辑(并可选取消监听)。
常见应用:懒加载、无限滚动、曝光统计、滚动动画,是前端性能优化和交互体验提升的重要 API。