Skip to content

浏览器内置API,ResizeObserver 的使用,用于监测DOM元素尺寸的变化

前言

某天,我正在 “欢快” 的开发需求,突然 tapd 接收个 Bug:某单据详情页内的展开按钮,在第二次进入页面时会突然消失。

我只能停下开发,开始定位,后面发现这个展开按钮时根据子元素的高度进行展示的,如果子元素高度没有到某个阈值,就不显示该按钮。

显示逻辑定位到了,现在的问题是为何只有在第二次进入页面才会消失?

通过一番调试,发现原因是:获取子元素的高度是通过给外层元素绑定 ref 获取的,在离开页面时,这个 ref 置为了 undefined,第二次进入页面还是 undefined 所以就拿不到子元素高度,也就不显示展开按钮。

只需要每次进入页面时,获取到子元素高度即可。这个时候就想到不久前才看到浏览器内置的API ResizeObserver,这东西可以检测元素尺寸变化从而触发回调,在进入页面时由于渲染,必然会监听到元素尺寸变化,所以可以在变化回调里面获取到子元素高度!

下面正式介绍

ResizeObserver 定义

ResizeObserver 是浏览器提供的一个 API,用于监听 DOM 元素尺寸的变化(如宽度、高度的改变)。

相比传统的 window.resize 事件(只能监听窗口大小变化),它可以直接观察特定元素或其子元素、边框盒(border box)、内容盒(content box)的尺寸变化。

基本用法

1. 创建 ResizeObserver 实例

通过回调函数定义当元素尺寸变化时的处理逻辑:

const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
// 处理每个变化的元素
}
});

2. 监听目标元素

const targetElement = document.getElementById("my-element");
observer.observe(targetElement);

3. 停止监听

停止监听特定元素:observer.unobserve(targetElement)

停止所有监听并销毁观察器:observer.disconnect()

4. 示例代码

<div id="resizable-box" style="width: 100px; height: 100px; background: lightblue;"></div>
<script>
const box = document.getElementById("resizable-box");
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`元素尺寸变化:宽度 ${width}px,高度 ${height}px`);
// 动态修改背景颜色
entry.target.style.background = width > 150 ? "salmon" : "lightblue";
}
});
observer.observe(box);
// 动态改变元素大小(测试用)
setTimeout(() => {
box.style.width = "200px";
}, 2000);
</script>
回调参数解析

回调函数的参数 entries 是一个数组,每个元素是一个 ResizeObserverEntry 对象,包含以下关键属性:

  • target: 尺寸发生变化的元素。

  • contentRect: 内容区域的尺寸信息(width, height, top, right, bottom, left)。

  • borderBoxSize: 边框盒的尺寸信息(需注意浏览器兼容性)。

  • contentBoxSize: 内容盒的尺寸信息(需注意浏览器兼容性)。

5. 应用场景

  • 响应式布局:元素尺寸变化时动态调整子元素布局。

  • 图表库:当图表容器大小变化时,重新渲染图表(如 ECharts、D3.js)。

  • 适应组件:如侧边栏折叠/展开时,调整内容区域布局。

6. 注意事项

1.兼容性

现代浏览器(Chrome 64+、Firefox 69+、Edge 79+、Safari 13.4+)均支持。

旧版浏览器需使用 polyfill

2.性能优化

避免在回调中执行耗时操作,必要时使用防抖(debounce)。

及时调用 unobserve()disconnect() 释放资源。

3.观测范围

默认监听 content-box(内容区域),可通过 box 选项修改观测类型:

7. 总结

通过 ResizeObserver,开发者可以更高效地响应元素尺寸变化,替代传统的轮询或基于 window.resize 的间接监听方案。

最后的解决代码

const childrenRef = useRef<HTMLDivElement>(null);
const [ childrenHeight, setChildrenHeight ] = useState(0);
useEffect(() => {
if (!childrenRef.current) return;
// 创建监听实例
const resizeObserver = new ResizeObserver((entries) => {
if (!entries.length) return;
// 找到对应监听的dom
const entry = entries.find((entry) => entry.target === childrenRef.current);
if (!entry) return;
setChildrenHeight(entry.contentRect.height);
});
// 监听元素
resizeObserver.observe(childrenRef.current);
return () => {
// 消除监听
resizeObserver.disconnect();
};
}, [ props.children ]);