在 Web 开发中,我们经常需要监听某些变化并作出响应——元素是否出现在视口中?DOM 树是否被修改?元素尺寸是否改变?
浏览器提供了三种 Observer API 来优雅地解决这些问题。
本文将全面介绍 IntersectionObserver、MutationObserver 和 ResizeObserver 的概念、用法及典型场景,并通过一个 ECharts 自适应案例,帮你彻底搞懂如何选择。
前言
过去,我们可能用 scroll 事件监听元素可见性(结合 getBoundingClientRect),用 Mutation Event 监听 DOM 变化(已被废弃),用 window.resize 事件配合 getComputedStyle 猜测元素尺寸变化。这些方式不仅性能差,而且代码复杂。
现代浏览器提供了三个专用的观察器 API:
- IntersectionObserver:观察元素与视口(或祖先元素)的交叉状态
- MutationObserver:观察 DOM 树的变化
- ResizeObserver:观察元素尺寸的变化
它们都是异步的,不会阻塞主线程,且提供了更精细的配置。下面我们逐一深入。
1. IntersectionObserver:观察元素的可见性
概念
IntersectionObserver 用于异步观察目标元素与其祖先容器或顶级文档视口的交叉状态。
简单说,就是监测元素是否出现在视口中,以及出现在视口中的比例。
用法
const observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);
- callback:当交叉状态变化时调用,接收一个
entries数组,每个 entry 包含目标的交叉信息(如intersectionRatio、isIntersecting等)。 - options:可配置根容器(
root)、阈值(threshold)、边距(rootMargin)等。
典型场景
- 图片懒加载:当图片元素进入视口时才开始加载真实图片
- 无限滚动:当页面滚动到底部附近的占位元素出现时,加载更多内容
- 曝光统计:记录广告或内容区块被用户看到的时长和次数
- 视差滚动/动画触发:当元素进入视口时播放动画
示例:懒加载
const lazyImages = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // 加载后停止观察
}
});
}, { rootMargin: '50px' }); // 提前 50px 触发
lazyImages.forEach(img => observer.observe(img));
2. MutationObserver:观察 DOM 变化
概念
MutationObserver 用于监听 DOM 树的变化,包括子节点的添加/移除、属性变化、文本内容变化等。
它可以观察一个元素及其子树。
用法
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
- callback:接收一个
mutations列表,每个 mutation 包含变化的类型(type)、添加/删除的节点、变化的属性等信息。 - config:指定要观察的变化类型(必须至少指定
childList、attributes或characterData中的一个),以及是否观察子树(subtree)等。
典型场景
- 监听动态插入的内容:例如第三方脚本往页面中添加广告,你可以监听到并对其进行样式调整
- 实现自定义的撤销/重做:记录 DOM 变化以便回退
- 监控 DOM 操作:调试或分析时,观察 DOM 是否被意外修改
- 与框架集成:当 DOM 被外部修改时,同步框架状态
示例:监听属性变化
const target = document.getElementById('my-element');
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes') {
console.log(`属性 ${mutation.attributeName} 发生变化`);
}
});
});
observer.observe(target, { attributes: true });
3. ResizeObserver:观察元素尺寸变化
概念
ResizeObserver 用于监听元素内容区域或边框区域的大小变化。
无论变化是由窗口 resize、JavaScript 操作、CSS 布局调整还是其他原因引起的,它都能可靠地触发回调。
用法
const observer = new ResizeObserver(callback);
observer.observe(targetElement);
- callback:接收一个
entries数组,每个 entry 包含目标的尺寸信息(如contentRect包含宽度、高度等)。
典型场景
- 响应式组件:当容器尺寸变化时,调整内部布局或重新渲染图表
- 监听 iframe 尺寸变化:自适应 iframe 高度
- 与第三方库集成:如 ECharts、D3.js 等需要在容器大小改变时重新绘制的库
- 自定义滚动条或虚拟列表:根据容器尺寸计算可视区域
示例:监听容器尺寸
const container = document.getElementById('chart');
const observer = new ResizeObserver(() => {
console.log('容器尺寸变化', container.offsetWidth, container.offsetHeight);
});
observer.observe(container);
4. 三者对比
| 维度 | IntersectionObserver | MutationObserver | ResizeObserver |
|---|---|---|---|
| 观察对象 | 元素相对于视口/祖先的可见性 | DOM 树的结构、属性、文本内容 | 元素的尺寸 |
| 触发时机 | 当元素进入/离开视口,或交叉比例超过阈值 | 当 DOM 节点被添加/删除、属性改变、文本内容改变 | 当元素内容区域或边框区域尺寸改变 |
| 回调参数 | IntersectionObserverEntry 数组(包含交叉比例、目标矩形等信息) | MutationRecord 数组(包含变化类型、新增节点、属性名等) | ResizeObserverEntry 数组(包含目标尺寸信息) |
| 主要用途 | 滚动相关优化(懒加载、无限滚动、曝光统计) | 监听动态 DOM 变化、自定义撤销、调试 | 响应式布局、图表自适应、容器尺寸监控 |
| 配置项 | 根容器、阈值、边距 | 子节点变化、属性变化、字符数据、子树等 | 无(可观察多个元素) |
| 兼容性 | 广泛支持 | 广泛支持 | 现代浏览器(可 polyfill) |
5. 实战案例:监听 ECharts 容器尺寸变化
假设我们有一个 ECharts 图表,希望容器尺寸改变时自动调用 chart.resize()。
方案 A:IntersectionObserver ❌
// 不可行!尺寸变化不会触发交叉观察器
new IntersectionObserver(() => chart.resize()).observe(container);
IntersectionObserver 只关心可见性,尺寸变化通常不会改变可见性比例(除非变化导致元素移出视口),因此无法可靠触发。
方案 B:MutationObserver ⚠️
// 只能捕获直接修改 style 或类名的场景
new MutationObserver(() => chart.resize()).observe(container, {
attributes: true,
attributeFilter: ['style', 'class']
});
// 还需额外监听 window resize(如果容器随窗口变化)
window.addEventListener('resize', () => chart.resize());
MutationObserver 无法捕获由父容器布局变化引起的尺寸调整(如 flex 布局),而且需要额外处理窗口 resize,容易遗漏或过度触发。
方案 C:ResizeObserver ✅
const chart = echarts.init(container);
new ResizeObserver(() => chart.resize()).observe(container);
简洁、可靠,无论尺寸变化原因是什么,都能准确触发。
结论:对于需要监听元素尺寸变化的场景,ResizeObserver 是标准且唯一正确的选择。
总结
- IntersectionObserver:用于元素可见性相关场景(懒加载、无限滚动、曝光统计等)
- MutationObserver:用于监听 DOM 结构或属性变化(动态内容、自定义撤销/重做、调试等)
- ResizeObserver:用于监听元素尺寸变化(响应式布局、图表自适应、容器尺寸监控等)
三者各司其职,没有优劣之分,只有是否适合当前需求。希望本文能帮你理清这三个 API 的区别,在合适的场景下做出正确选择。