12k
All articles

ResizeObserver 与 Window Resize:何时使用哪个

对比 ResizeObserver 与 window resize 事件,针对视口变化、元素尺寸追踪及 CSS 容器查询场景,分析各自的适用时机。

OpenReplay Team
OpenReplay Team
ResizeObserver 与 Window Resize:何时使用哪个

你正在构建一个图表组件,需要在其容器缩小时重新绘制。你使用 window.addEventListener('resize', ...),它大部分时候都能工作——直到面板因侧边栏折叠而调整大小时,却没有任何事件触发。这就是本文要解决的核心问题。

核心要点

  • window resize 事件仅在浏览器视口改变大小时触发。它对单个 DOM 元素一无所知。
  • ResizeObserver 监视特定元素,并在其尺寸发生变化时触发,无论变化原因是什么。
  • 在视口调整大小后轮询元素尺寸是脆弱的——它会错过由 CSS 切换、动态内容、字体加载等引起的变化。
  • 对于不需要 JavaScript 的基于容器的样式设置,CSS 容器查询是首选工具。

每个 API 实际观察什么

window resize 事件浏览器视口改变大小时触发。这就是它的全部作用范围。它对单个 DOM 元素一无所知。

ResizeObserver 监视特定元素,并在其尺寸发生变化时触发——无论是什么原因导致的。视口调整大小、DOM 插入、CSS 类切换、父级 flexbox 重排——如果元素的大小发生变化,所有这些都会触发观察器。

这是根本区别。它们观察不同的东西,通常用于不同的场景。

何时使用 Window Resize 事件

当你的逻辑真正依赖于视口尺寸,而不是任何特定元素的大小时,window resize 事件是正确的工具:

  • 在视口断点处切换移动导航布局
  • 将全页面 canvas 或 WebGL 上下文调整为 window.innerWidth / window.innerHeight
  • 调整依赖于屏幕空间的全局布局状态
window.addEventListener('resize', () => {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight
  render()
})

对于纯视觉的、由 CSS 驱动的对视口大小的响应,完全优先使用媒体查询而不是 JavaScript。

何时使用 ResizeObserver

ResizeObserver 的使用场景集中在必须响应元素实际渲染大小的组件级行为:

  • 当容器宽度变化时重新计算坐标轴比例的图表
  • 根据可用空间调整工具栏布局的文本编辑器
  • 可调整大小的面板或分割窗格小部件
  • 任何不控制自己容器的嵌入式小部件
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { inlineSize, blockSize } = entry.contentBoxSize[0]
    chart.resize(inlineSize, blockSize)
  }
})

ro.observe(document.querySelector('.chart-container'))

理解现代 API 形式

当前的 ResizeObserver API 在每个条目上暴露 contentBoxSizeborderBoxSize 数组——每个都包含 inlineSize(水平书写模式下的宽度)和 blockSize(高度)。较旧的 contentRect 属性仍然有效,但主要是为了向后兼容。在新代码中优先使用 contentBoxSize[0]

为什么不在 Window Resize 后轮询?

一个常见的模式——监听 window resize 事件,然后对元素调用 getBoundingClientRect()——是脆弱的。它会错过与视口无关的大小变化:动态内容加载、CSS 类切换、注入的子元素、字体加载。ResizeObserver 能捕获所有这些。视口调整大小后的轮询则不能。

性能和反馈循环

ResizeObserver 回调在布局之后、绘制之前运行,这意味着回调内的 DOM 读取反映当前布局,而不会强制重排。这在结构上比在 resize 事件处理程序内读取布局属性更安全。

一个注意事项:如果你在观察元素自己的回调内修改其大小,会触发另一次观察。ResizeObserver 通过在同一帧内以逐渐更深的 DOM 深度处理观察来处理这个问题,如果循环无法解决,最终会报告错误——防止无限循环。你仍然应该谨慎对待在回调内写入的内容。

当你的回调涉及布局或元素大小变化独立于视口调整大小发生时,ResizeObserver 的性能优势变得有意义。

CSS 容器查询替代方案

如果你的目标是根据容器的大小设置元素样式,CSS 容器查询是正确的工具——不需要 JavaScript。将 ResizeObserver 保留给运行时逻辑(而非样式)必须响应大小变化的情况。

场景最佳工具
视口范围的布局变化window resize 事件
元素尺寸驱动 JS 逻辑ResizeObserver
仅基于容器的样式设置CSS 容器查询

结论

当你关心视口时使用 window resize 事件。当你关心元素时使用 ResizeObserver。当你只需要改变样式时使用 CSS 容器查询。这三个工具覆盖了响应式行为的完整空间——选择与你实际测量内容匹配的那个。

常见问题

我可以使用 ResizeObserver 检测元素何时变为可见或隐藏吗?

ResizeObserver 在元素尺寸变化时触发,因此将元素折叠为零宽度或零高度会触发它。但是,它不会因保持尺寸不变的可见性或不透明度变化而触发。对于真正的可见性检测,请改用 IntersectionObserver。

从 DOM 中移除元素时需要断开 ResizeObserver 吗?

一旦元素被垃圾回收,观察器会自动停止跟踪它。但是,显式调用 unobserve 或 disconnect 是良好实践,特别是在具有挂载和卸载生命周期的单页应用程序或框架组件中,以避免过时引用和意外回调。

所有现代浏览器都支持 ResizeObserver 吗?

是的。ResizeObserver 在 Chrome、Firefox、Safari 和 Edge 中都有广泛支持。如果需要支持较旧的浏览器,可以使用 polyfill,但它在内部依赖轮询和 mutation observers,因此无法匹配原生性能。

我应该对 ResizeObserver 回调进行节流或防抖吗?

对于轻量级 UI 更新通常不需要。ResizeObserver 已经在渲染管道内高效地批处理通知。但是,如果你的回调触发昂贵的下游工作,如网络请求或繁重计算,应对该特定工作进行防抖,而不是整个回调。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.