ResizeObserver / MutationObserver / IntersectionObserver 深度对比。
在前端开发中,监听元素变化是一种非常常见但经常被忽视的需求。从响应式布局、动画触发、内容同步、懒加载,到可视化组件自适应,一个共同的问题摆在我们面前:
"元素变了,我必须马上知道!"
但你真的选对方式了吗?你用的方式够高效吗?是不是还在偷偷用 setInterval
+ getBoundingClientRect
这种"偷鸡法"?
为什么"监听元素变化"成了刚需?
假设你正在开发以下功能模块:
- 📊 页面容器变动 → 图表自适应/数据渲染;
- 🧱 侧边栏折叠/展开 → 主区域重新布局;
- 💤 图片懒加载 → 检测进入视口;
- 📝 富文本编辑器 → 实时内容监听与保存;
- 🧩 微前端子应用 → 激活后动态渲染布局。

这些场景背后隐藏的逻辑其实只有一句话:
当 DOM 的尺寸、结构、位置、属性或可见性发生变化时,第一时间捕捉并响应。
6 种主流监听方式横评对比
变化类型 | 推荐方案 | 性能表现 | 是否原生 | 推荐指数 |
---|---|---|---|---|
元素尺寸变化 | ✅ ResizeObserver |
⭐⭐⭐⭐ | ✅ | 🌟🌟🌟🌟🌟 |
DOM结构或文本变化 | ✅ MutationObserver |
⭐⭐⭐ | ✅ | 🌟🌟🌟🌟 |
属性变化(class/style) | ✅ MutationObserver |
⭐⭐⭐ | ✅ | 🌟🌟🌟🌟 |
元素是否进入视口 | ✅ IntersectionObserver |
⭐⭐⭐⭐⭐ | ✅ | 🌟🌟🌟🌟🌟 |
元素位置变化 | ❗ getBoundingClientRect + 定时器 |
⭐ | ❌ | ⚠️ 勉强可用 |
框架内部响应式场景 | ✔️ 框架响应式系统 / 事件总线 | 可控 | ❌ | 🌟🌟🌟 |
实战讲解 + 场景推荐

1、尺寸变化场景:图表自动适配、容器自渲染
推荐方案:ResizeObserver
ts
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
console.log('尺寸变化:', entry.contentRect);
renderChart(entry.target); // 例如重绘图表
}
});
ro.observe(document.querySelector('#chart-container'));
✅ 优势:
- 准确监听元素宽高变化 ;(监听元素尺寸变化的首选)
- 比
window.onresize
更粒度细; - 原生支持、性能好,异步回调,浏览器自动合并变动。
⚠️ 注意事项:
- 不监听位置变化;
- 有些老旧浏览器(如 IE)不支持,需做兼容处理。
2、DOM结构变化:组件嵌套、评论区更新、文档实时编辑
推荐方案:MutationObserver
ts
const observer = new MutationObserver(mutations => {
for (let m of mutations) {
console.log('DOM 变动:', m);
}
});
observer.observe(targetEl, {
childList: true,
characterData: true,
subtree: true
});
✅ 优势:
- 可监听文本变更、节点增删、属性变化;
- 用于富文本编辑器、评论列表更新、组件内部状态监控。
⚠️ 注意事项:
- 会监听到很多冗余变化,需合理筛选触发条件;
- 性能依赖 DOM 操作频率,不建议监听整个
body
节点。
3、可见性变化:懒加载、广告曝光、滚动动画触发
推荐方案:IntersectionObserver
ts
const io = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('进入视口:', entry.target);
}
});
});
io.observe(document.querySelector('#lazy-img'));
✅ 优势:
- 可判断元素是否在可视区域;
- 支持 rootMargin、threshold 等灵活配置;
- 性能极佳,原生能力,浏览器内部优化处理;
- 非常适合图片懒加载、曝光埋点、滚动触发动画;
4、最不推荐的方案:定时器 + getBoundingClientRect()
哈哈哈!我还真这么用过(easy),你用过吗?
ts
let lastTop = 0;
setInterval(() => {
const rect = el.getBoundingClientRect();
if (rect.top !== lastTop) {
console.log('位置发生变化');
lastTop = rect.top;
}
}, 200);
❌ 问题:
- 性能低:频繁访问布局属性会触发 强制重排(reflow) ;
- 易错:无法捕捉瞬间变化,尤其是动画类变化;
- 不优雅:属于"hack"方式,维护成本高。
✅ 替代建议:
- 位置相关需求考虑转为响应"尺寸 + 视口";
- 或结合滚动事件与
IntersectionObserver
处理。
既然已经知道,那么以后就不能使用这种方式。我们可以根据实际情况,选择最合适的监听方式。
应用建议:如何选择最合适的监听方式?
你遇到的需求 | 推荐监听方式 |
---|---|
元素尺寸变化 → 重绘图表 | ResizeObserver |
DOM 结构/文本变更 → 内容同步 | MutationObserver |
图片进入视口 → 懒加载 | IntersectionObserver |
样式变化 → 触发样式逻辑 | MutationObserver |
元素位置变动 → 弹窗重新定位 | 考虑组合 Resize + Intersection |
万不得已 → setInterval + Rect | ❗仅限应急,建议重构 |
当然,除了以上方式,我们还可以借助响应式框架(Vue、React),来实现自定义事件或数据驱动组件。
总结:
如果你还在用 setInterval
监听元素位置,或无限制使用 MutationObserver
监听全页面 DOM,那很可能已经埋下了性能隐患。
👇👇👇
欢迎在评论区分享你的使用场景、踩过的坑、甚至提出你还在困惑的问题,我们一起探讨!