ResizeObserver
元素尺寸变了就通知你,不用再监听
window.resize。
为什么不用 window.resize
window.resize 只在视口尺寸变化时触发。但元素尺寸变化的原因太多了:
- 侧边栏折叠,主内容区变宽
- 内容变多,把容器撑开
- flex / grid 重新分配空间
- 字体加载完成,文字重新排版
这些都不会触发 window.resize。ResizeObserver 观察的是元素本身,不管尺寸是被谁改的。
基本用法
js
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(width, height);
}
});
ro.observe(document.querySelector(".chart"));entry.contentRect 是内容盒,不含 padding 和 border。需要边框盒就用 borderBoxSize:
js
const ro = new ResizeObserver((entries) => {
const box = entries[0].borderBoxSize[0];
console.log(box.inlineSize, box.blockSize);
});
ro.observe(document.querySelector(".chart"));borderBoxSize 是数组——一个目标元素在某些布局下可能对应多个盒(比如分列),取 [0] 就行。
场景:canvas 自适应
js
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const ro = new ResizeObserver(() => {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
// 物理像素尺寸
canvas.width = Math.round(rect.width * dpr);
canvas.height = Math.round(rect.height * dpr);
// CSS 尺寸
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
// 改完 width/height 画布会被重置,重新设置缩放
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
draw();
});
ro.observe(canvas);不处理 DPR 的话,canvas 在高分屏上会糊。这里的顺序不能反:先设物理像素,再设 CSS 尺寸,最后重置 transform。
场景:textarea 自增高
js
const ro = new ResizeObserver(() => {
textarea.style.height = "auto";
textarea.style.height = `${textarea.scrollHeight}px`;
});
ro.observe(textarea);这段代码有个隐患:回调里改的正是被观察元素的尺寸,会再次触发回调。高度最终会稳定下来,但如果 CSS 里有 min-height、box-sizing 之类的干扰,就可能来回震荡,控制台报 ResizeObserver loop completed with undelivered notifications。更稳妥的写法是把写操作放进 requestAnimationFrame,或者只在高度真的变化时才写。
坑
- 别在回调里改被观察元素的尺寸,容易触发上面的 loop 错误。必须改就延到
requestAnimationFrame,或者用标记位避免重复。 contentRect和getBoundingClientRect()不是一回事。前者是内容盒、整数;后者包含 padding 和 border,是小数。- 观察
documentElement要小心。滚动条出现/消失会改变它的尺寸,可能递归触发。监听视口用window.resize或visualViewport.resize更直接。 - 组件卸载时
disconnect(),和 IntersectionObserver 一样,否则回调闭包里引用的状态释放不掉。 - 能用 CSS 就别用 JS。容器查询(container queries)已经能处理大部分「根据父容器宽度切换样式」的需求,纯 CSS 更省事。ResizeObserver 适合确实需要拿到具体数值的场景:canvas、图表、虚拟列表。
