章
目
录
前端开发监听元素尺寸变化是个常见需求,以往,不少开发者会用轮询
或者window.resize
来实现这个功能。但今天要给大家介绍一个更专业、更高效的原生API——ResizeObserver
,要是你还没用过,那可就真的out啦!
一、ResizeObserver解决了什么难题?
在ResizeObserver
出现之前,监听元素尺寸变化的方法有不少,像window.addEventListener('resize')
、用定时器轮询DOM尺寸、借助MutationObserver
搭配逻辑判断,还有使用第三方库等。不过,这些方法都存在一个“硬伤”:它们监听的其实是“窗口变化”或者“DOM结构变化”,没办法精准地监听元素自身尺寸的变化。就拿window.addEventListener('resize')
来说吧,看下面这段代码:
// 👎 无法监听特定元素尺寸变化
window.addEventListener('resize', () => {
console.log('窗口变化了,但某个元素变没变不知道');
});
从这段代码可以看出,窗口变化时,我们根本不知道页面里某个特定元素的尺寸有没有变化。
而ResizeObserver
的出现,就是专门来解决这个问题的。看下面的代码:
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
console.log('元素尺寸变化:', entry.contentRect);
}
});
ro.observe(document.querySelector('#target'));
ResizeObserver
基于浏览器底层布局机制,能异步执行操作,对性能十分友好,它专注于监听DOM元素尺寸本身的变化。只要被监听的元素尺寸有变动,就能及时捕捉到,并且获取到变化后的尺寸信息。
二、ResizeObserver的应用场景
(一)图表重绘
在使用像ECharts、Highcharts这些图表库时,当图表容器的尺寸发生变化,比如缩放、切换布局或者隐藏后再显示,如果不及时处理,图表可能会被挤压变形,影响展示效果。这时候ResizeObserver
就派上用场了。看下面的代码:
const chart = echarts.init(document.getElementById('chart'));
const ro = new ResizeObserver(() => {
chart.resize();
});
ro.observe(document.getElementById('chart'));
这段代码中,通过ResizeObserver
监听图表容器的尺寸变化,一旦容器尺寸改变,就会自动调用chart.resize()
方法重新绘制图表,让图表始终保持良好的展示效果,再也不用依赖window.resize
啦!
(二)响应式布局组件
对于按钮组、菜单、卡片布局这类组件,在容器宽度发生变化时,可能需要切换布局模式。用ResizeObserver
来实现,逻辑会更加清晰。比如下面这段代码:
ro.observe(container);
ro.callback = entries => {
const width = entries[0].contentRect.width;
toggleLayout(width > 600 ? 'row' : 'column');
};
在这段代码里,通过监听容器元素的尺寸变化,根据宽度来决定是采用row
还是column
布局模式,比起传统的resize + getBoundingClientRect()
方式,代码简洁明了不少。
(三)复杂布局场景
在嵌套iframe、自适应容器等复杂布局场景中,ResizeObserver
的优势就更加明显了。像iframe中的元素尺寸变化,用window
是捕捉不到的,但ResizeObserver
却可以轻松实现。而且,它还支持组件嵌套、shadow DOM,在组合微前端、构建复杂布局系统时都能发挥重要作用。
三、ResizeObserver与其他监听方式的对比
为了让大家更直观地了解ResizeObserver
的优势,下面通过表格对比一下它和其他监听方式:
功能需求 | ResizeObserver | window.resize | MutationObserver | setInterval |
---|---|---|---|---|
元素尺寸变化监听 | ✅ 支持 | ❌ 不支持 | ⚠️ 需判断尺寸 | ⚠️ 不推荐 |
性能表现 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ | ⭐ |
精确度 | 高 | 低 | 中 | 低 |
实时性 | 高 | 中 | 中 | 低 |
编码复杂度 | 低 | 低 | 中 | 高 |
从表格中可以看出,在监听元素尺寸变化方面,ResizeObserver
无论是性能、精确度还是实时性,都表现得相当出色,编码复杂度也较低。
四、ResizeObserver高阶使用技巧
(一)防抖处理:控制触发频率
在一些高频率布局变化的场景,比如拖动元素或者动态内容渲染时,频繁触发ResizeObserver
的回调函数可能会导致性能问题。这时候可以通过防抖处理来控制触发频率。看下面的代码:
const debounce = (fn, delay = 100) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
const ro = new ResizeObserver(debounce(entries => {
// 执行逻辑
}));
在这段代码里,debounce
函数实现了防抖功能,当ResizeObserver
监听到尺寸变化时,并不会立即执行回调函数,而是等待delay
时间(这里设置为100毫秒),如果在这段时间内又有新的变化,就会取消之前的定时器重新计时,只有当超过delay
时间没有新变化时,才会执行回调函数,这样就能有效控制触发频率了。
(二)多元素监听 + 状态管理
在处理组件列表、自定义容器池、栅格系统这类场景时,可能需要同时监听多个元素的尺寸变化,并管理它们的状态。下面这段代码展示了如何实现:
const sizeMap = new Map();
const ro = new ResizeObserver(entries => {
for (const entry of entries) {
const old = sizeMap.get(entry.target);
const now = entry.contentRect;
if (!old || old.width !== now.width) {
sizeMap.set(entry.target, now);
handleResize(entry.target);
}
}
});
这里使用Map
来存储每个元素的尺寸状态,当ResizeObserver
监听到元素尺寸变化时,会对比当前尺寸和之前存储的尺寸,如果有变化,就更新状态并执行相应的处理函数handleResize
。
(三)结合生命周期动态绑定(Vue、React)
在Vue、React或者Web Component开发中,为了避免内存泄漏,需要在组件的生命周期中正确地绑定和解绑ResizeObserver
。看下面的代码:
onMounted(() => {
ro.observe(elRef.value);
});
onBeforeUnmount(() => {
ro.unobserve(elRef.value);
});
在组件挂载时,通过onMounted
钩子函数监听元素尺寸变化;在组件卸载前,利用onBeforeUnmount
钩子函数取消监听,这样就能确保内存的合理使用,避免出现内存泄漏问题。
五、使用ResizeObserver的注意事项
虽然ResizeObserver
功能强大,但在使用过程中也有一些需要注意的地方:
- 避免同步读取布局:尽量不要在回调函数中同步读取布局相关属性,比如
offsetHeight
。因为这样可能会引发强制回流,导致性能抖动,影响页面的流畅性。 - 控制多元素监听频率:当监听多个元素尺寸变化时,最好结合防抖或者节流技术,控制回调函数的触发频率,防止因为频繁回调而造成页面卡顿。
- 正确绑定和解绑:在组件开发中,一定要在组件的生命周期里正确地绑定和解绑
ResizeObserver
,防止出现内存泄漏,保证程序的稳定性。 - 兼容性问题:IE浏览器不支持
ResizeObserver
,如果项目需要兼容IE,可以使用Polyfill进行兼容处理,比如resize-observer-polyfill
。
总的来说,ResizeObserver
是一个轻量、精准且高效的尺寸变化监听方案,希望大家都能试试这个强大的API!