如何监听DOM尺寸的变化?element-resize-detector 和 resizeObserver

远古方案:resize的弊端

  1. 全局监听,粒度粗糙
    window.resize 是全局事件,任何窗口尺寸变化都会触发回调,即使页面中目标元素并未发生实际变化。开发者需手动遍历并比对所有关注元素的尺寸,逻辑冗余且效率低下。

  2. 频繁触发,易导致性能瓶颈

    用户拖拽调整窗口时,resize 事件会高频连续触发,若回调中包含复杂计算或 DOM 操作,极易引发页面卡顿。虽可通过节流(throttle)优化,但仍无法从根本上解决问题。

  3. 无法感知非窗口引起的元素变化

    当元素因内容更新、CSS 动画、父容器结构调整等非窗口 resize 行为导致尺寸变化时,该事件完全无法捕获,存在严重的监听盲区。

下面这段代码,是旧版本的vben-admin封装的echarts的hooks,使用了监听window.resize的方式监听屏幕尺寸变化,从而去重新渲染echarts图表:

js 复制代码
 function initCharts(t = theme) {
    const el = unref(elRef);
    if (!el || !unref(el)) {
      return;
    }

    chartInstance = echarts.init(el, t);
    const { removeEvent } = useEventListener({
      el: window,
      name: 'resize',
      listener: resizeFn,
    });
    removeResizeFn = removeEvent;
    const { widthRef, screenEnum } = useBreakpoint();
    if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
      useTimeoutFn(() => {
        resizeFn();
      }, 30);
    }
  }

可见大部分人的第一想法还是用resize这个深入人心的方案。

完善的第三方库------element-resize-detector

element-resize-detector 的底层原理是通过在目标元素内部创建隐藏的 <object> 或监听滚动事件来间接检测尺寸变化‌,从而规避浏览器无法原生监听元素 resize 的限制。

使用方式:

js 复制代码
import elementResizeDetectorMaker from "element-resize-detector";
const erd = elementResizeDetectorMaker();

const chartContainer = document.getElementById("chartContainer");

// 模拟图表绘制函数
function renderChart(width, height) {
    console.log(`重新绘制图表:宽${width}px,高${height}px`);
    // 实际项目中此处可调用ECharts、HighCharts等图表库的重绘方法
}

// 监听元素尺寸变化 回调函数里重绘
erd.listenTo(chartContainer, function(element) {
    const width = element.offsetWidth;
    const height = element.offsetHeight;
    renderChart(width, height);
})

具体实现依赖两种核心策略:

  1. 滚动(scroll)策略(默认)
    在目标元素内动态插入一个隐藏的 <div>,并设置其宽高略大于父元素,同时监听该元素的 scroll 事件。当目标元素尺寸发生变化时,内部元素的可滚动区域随之改变,从而触发 scroll 事件,实现对尺寸变化的捕获。该方式性能高、兼容性好,适用于绝大多数现代浏览器。

具体步骤:

  • 初始化监听事件和初始的尺寸

  • 注入滚动元素

  • 注册监听:registerListenersAndPositionElements里监听了渲染、收缩、扩展事件

    js 复制代码
    getState(element).onRendered = handleRender;
    getState(element).onExpand = handleScroll;
    getState(element).onShrink = handleScroll;
    js 复制代码
    function install() {
      initListeners();
      storeStartSize();
    
      batchProcessor.add(0, storeStyle);
      batchProcessor.add(1, injectScrollElements);
      batchProcessor.add(2, registerListenersAndPositionElements);
      batchProcessor.add(3, finalizeDomMutation);
      batchProcessor.add(4, ready);
    }
  1. 对象(object)策略
    将一个隐藏的 <object> 标签嵌入目标元素内部,并指向当前页面。<object> 元素会自动填充其容器,当容器尺寸变化时,<object> 会触发自身的 resize 事件。由于 <object> 支持监听 resize,因此可借此间接感知父元素的尺寸变化。此方法主要用于兼容老旧浏览器(如 IE)。

核心逻辑:

js 复制代码
// IE8 直接给元素绑定resize事件
if(browserDetector.isIE(8)) {
  //IE 8 does not support object, but supports the resize event directly on elements.
  getState(element).object = {
    proxy: listenerProxy
  };
  element.attachEvent("onresize", listenerProxy);
} else {
  var object = getObject(element);
  if(!object) {
    throw new Error("Element is not detectable by this strategy.");
  }
  object.contentDocument.defaultView.addEventListener("resize", listenerProxy);
}

对比:

策略名称 优点 缺点
object 精确性高 性能相对差,尤其是同一个页面有多个元素需要监听时
scroll 性能较好 精确性较低、特定布局无法使用(源码中有大量的兼容性处理)

现代浏览器的处理方案------resizeObserver------以及简单的封装

ResizeObserver ‌ 是现代浏览器提供的原生 API,用于‌监听 DOM 元素内容区域的尺寸变化‌,无需依赖轮询或事件冒泡,具有高精度、高性能的特点。

  1. 精准监听元素级尺寸变化

    ResizeObserver 能直接监听任意 DOM 元素的 content-boxborder-box 尺寸变化,包括由 CSS 动画、内容更新、布局重排等引起的改变,‌无需手动计算或依赖窗口 resize 事件‌。

  2. 异步回调,避免性能瓶颈

    回调函数通过 requestAnimationFrame 异步执行,不会阻塞渲染线程,即使在高频变化场景下也能保持页面流畅。

  3. 自动忽略不可见元素

    对于 display: none 或未插入文档的元素,ResizeObserver 会自动暂停监听,减少无效计算。

  4. 支持批量处理与资源清理

    • observe(element):开始监听指定元素。
    • unobserve(element):停止监听某个元素。
    • disconnect():断开所有监听,释放资源。

使用方式:

js 复制代码
const observer = new ResizeObserver((entries) => {
  entries.forEach((entry) => {
    const { width, height } = entry.contentRect; // content-box 尺寸
    console.log(`内容区域尺寸:${width}x${height}`);
  });
});

// 开始监听
observer.observe(document.getElementById('target'));

兼容性问题:

resizeObserver的浏览器支持情况如下: Chrome: 从版本 64 开始支持(2018 年 1 月发布) Firefox: 从版本 69 开始支持(2019 年 9 月发布) Edge: 从版本 79 开始支持(2020 年 1 月发布)。 Safari: 从版本 13.1 开始支持(2020 年 3 月发布)。 Opera: 从版本 51 开始支持(2018 年 1 月发布)。

若需要支持更低版本的浏览器,需要引用polyfill文件。

简单的封装:

js 复制代码
import ResizeObserver from 'resize-observer-polyfill';

function resizeHandler(entries: any[]) {
  for (const entry of entries) {
    const listeners = entry.target.__resizeListeners__ || [];
    if (listeners.length) {
      listeners.forEach((fn: () => any) => {
        fn();
      });
    }
  }
}

export function addResizeListener(element: any, fn: () => any) {
  if (!element.__resizeListeners__) {
    element.__resizeListeners__ = [];
    element.__ro__ = new ResizeObserver(resizeHandler);
    element.__ro__.observe(element);
  }
  element.__resizeListeners__.push(fn);
}

export function removeResizeListener(element: any, fn: () => any) {
  if (!element || !element.__resizeListeners__) return;
  element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
  if (!element.__resizeListeners__.length) {
    element.__ro__.disconnect();
  }
}

上面的封装是为了处理同一个元素多次监听,避免造成监听的覆盖。封装之后,用法和addEventListener比较相似,只是无需传入事件类型。

使用方式:

js 复制代码
const chartContainer = document.getElementById("chartContainer");

// 模拟图表绘制函数
function renderChart() {
    console.log(`重新绘制图表`);
    // 实际项目中此处可调用ECharts、HighCharts等图表库的重绘方法
}

// 监听元素尺寸变化 回调函数里重绘
addResizeListener(chartContainer, function() {
    renderChart()
})
相关推荐
胡志辉2 小时前
本地 AI 编码助手从 0 配起来:先选模型,再接 Ollama、VS Code、Claude Code 和 Codex
前端·后端
一颗小青松2 小时前
uniapp输入框fixed定位,导致页面顶起解决方案
前端·uni-app
孟陬2 小时前
从 Claude Code 187 种说“正在处理”的方式看一流公司的用户体验
前端·claude·bun
一楼的猫3 小时前
从工具链视角对比:番茄作家助手 vs 第三方写作辅助方案
java·服务器·开发语言·前端·学习·chatgpt·ai写作
掘金一周3 小时前
想换一辆电车,JYM有什么推荐 | 沸点周刊 5.21
前端·人工智能·后端
Nian.Baikal3 小时前
Cesium 3D Tiles 加载与优化实战
前端·cesium
KaMeidebaby4 小时前
卡梅德生物技术快报|噬菌体肽库展示技术构建 Mhp168‑Hsp70 定向随机肽库:流程、质控与数据结果
前端·数据库·其他·百度·新浪微博
lchcy4 小时前
前端实现单点登录(SSO登录)
前端
卷帘依旧4 小时前
SPA下的路由模式详解
前端