当前位置:首页 > 文章列表 > 文章 > 前端 > 前端 ResizeObserver 怎样读取内容盒和边框盒尺寸

前端 ResizeObserver 怎样读取内容盒和边框盒尺寸

来源:17golang原创 2026-10-20 02:45:22 0浏览 收藏

我在做可伸缩卡片时遇到过一个很容易误判的问题:CSS 里把 box-sizing、内边距和边框都配好了,ResizeObserver 回调里却仍然拿到“少了一圈”的宽度。后来我把目标拆成一句话:需要内容区就读 contentBoxSize,需要元素外轮廓就读 borderBoxSize,旧实现再回退到 contentRect。这样组件的尺寸计算才不会和视觉边界错位。

官方资料地址:https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserverEntry

要点速览
  • contentBoxSize 表示内容盒,通常不包含 padding 和 border;borderBoxSize 包含内容、内边距和边框。
  • 现代实现中的尺寸字段是数组,实际读取时优先取第一个碎片的 inlineSize 与 blockSize。
  • 需要兼容较早实现时,保留 contentRect.width 与 contentRect.height 回退,但不要把它和 border box 混为一谈。

先选对要读取的尺寸盒

ResizeObserver 观察的是元素尺寸,不是只有窗口变化才触发的全局事件。真正开始写回调前,先确认下游要回答哪个问题:如果要决定文字区域、绘图区域或内容列数,通常关心内容盒;如果要让浮层、描边或卡片外框和元素整体对齐,通常关心边框盒。

CSS 盒模型可以从内向外理解:内容盒是内容所在的区域,padding 在它外面,border 再包住 padding。于是同一个元素可能同时有两个合理的宽度。把内容区宽度拿去和外框定位,会出现间距被少算;把边框盒宽度拿去计算文字列数,又可能让内容判断偏大。

ResizeObserver 内容盒与边框盒包含关系的静态结构说明图
图1:ResizeObserver 两种尺寸盒的静态结构说明图,不是浏览器截图或运行证据。

最小配方:用 box 选项观察元素

observe 的第二个参数可以指定观察的盒类型。需要内容盒时传 content-box,需要边框盒时传 border-box。如果省略选项,默认观察内容盒,这正是很多“看起来少了边框”的来源。

const panel = document.querySelector('.panel');

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const size = entry.borderBoxSize?.[0];
    if (!size) continue;

    // 用边框盒驱动外框相关布局,避免把 padding 和 border 漏掉。
    panel.dataset.width = String(Math.round(size.inlineSize));
    panel.dataset.height = String(Math.round(size.blockSize));
  }
});

// box 只影响观察目标的尺寸盒,不会改变元素本身的 CSS 盒模型。
observer.observe(panel, { box: 'border-box' });

这里使用 inlineSize 和 blockSize,而不是把宽高写死成 width 与 height。逻辑尺寸字段能适应不同书写模式;如果你的组件明确只服务水平书写,也可以在最终业务层把它映射成横向宽度和纵向高度。

兼容数组与旧实现

现代 ResizeObserverEntry 的 contentBoxSize 和 borderBoxSize 是数组,原因是一个元素可能被分成多个布局碎片,例如多列场景。普通卡片大多只有一个碎片,因此常用 [0]。但较早的实现曾经把字段暴露成单个对象,所以兼容读取函数应同时处理数组和对象。

function firstBoxSize(value) {
  if (!value) return null;

  // 兼容现代数组形态,也兼容旧实现直接返回单个对象的情况。
  return Array.isArray(value) ? value[0] ?? null : value;
}

function readObservedSize(entry, box = 'content') {
  const source = box === 'border' ? entry.borderBoxSize : entry.contentBoxSize;
  const logical = firstBoxSize(source);
  if (logical) {
    // 逻辑尺寸字段比硬编码 width/height 更适合不同书写模式。
    return {
      inline: logical.inlineSize,
      block: logical.blockSize,
      source: `${box}-box`
    };
  }

  if (entry.contentRect) {
    // contentRect 是兼容回退,语义接近内容盒,不要当成边框盒。
    return {
      inline: entry.contentRect.width,
      block: entry.contentRect.height,
      source: 'content-rect-fallback'
    };
  }

  return null;
}

回退分支的关键不是“保证一定拿到一个数字”,而是保留尺寸语义。contentRect 适合兼容只提供旧字段的浏览器;如果你的业务必须精确读取 border box,就应当在不支持 borderBoxSize 时明确降级策略,而不是静默把内容盒当作边框盒。

ResizeObserverEntry 中 contentBoxSize borderBoxSize 与 contentRect 回退关系的静态说明图
图2:ResizeObserverEntry 尺寸字段与兼容回退的静态关系图,不是截图或运行证据。

把尺寸用于组件布局时,我会这样分层

读取尺寸后不要在回调里到处修改样式。我的习惯是先把观察结果归一化成一个小对象,再交给组件自己的布局函数。这样下游不需要知道当前浏览器返回的是数组、单对象还是 contentRect。

const card = document.querySelector('.card');

function applyCardLayout(size) {
  if (!size) return;

  // 这里的阈值只服务示例:内容区足够宽时显示双列。
  card.classList.toggle('is-wide', size.inline >= 560);
  card.style.setProperty('--observed-inline-size', `${size.inline}px`);
}

const cardObserver = new ResizeObserver(([entry]) => {
  // 组件只消费归一化结果,不关心 ResizeObserverEntry 的兼容细节。
  applyCardLayout(readObservedSize(entry, 'content'));
});

cardObserver.observe(card, { box: 'content-box' });

// 组件销毁时停止观察,避免保留不再使用的节点引用。
function destroyCardObserver() {
  cardObserver.disconnect();
}

这段代码的选择是有条件的:双列判断依赖内容区,因此观察 content-box;如果要计算卡片外框与相邻元素的贴合位置,就把观察选项和读取参数一起改为 border。不要只改一处,否则代码会出现“订阅了边框盒,读取却按内容盒解释”的隐性错位。

几个容易踩到的兼容坑

为什么读取 entry.contentBoxSize.width 会是 undefined?

现代字段通常是数组,应先取 entry.contentBoxSize[0]。为了兼容较早实现,可以用上面的 firstBoxSize 同时处理数组和单对象。

contentRect 能代替 borderBoxSize 吗?

不能直接等价替代。它更接近内容盒语义,适合作为旧实现回退;边框宽度或内边距不为零时,两者的数值可能不同。

什么时候需要关心多个数组项?

普通单列卡片通常只读第一项即可。元素存在多列或碎片化布局时,应该根据业务决定是累加、取最大值,还是逐碎片处理,不能默认第一项代表全部布局结果。

观察器回调里能不能直接改尺寸?

可以更新布局,但要避免让更新再次改变被观察元素的尺寸并形成反馈循环。更稳妥的做法是比较新旧值、只在状态确实变化时写回,并在组件销毁时调用 disconnect()。

完整片段:统一读取两种尺寸盒

function readBox(entry, box) {
  const raw = box === 'border' ? entry.borderBoxSize : entry.contentBoxSize;
  const item = Array.isArray(raw) ? raw[0] : raw;

  if (item) {
    // 优先使用标准逻辑尺寸字段。
    return { inline: item.inlineSize, block: item.blockSize };
  }

  if (entry.contentRect) {
    // 旧实现回退到内容区矩形;调用方应记录这是降级结果。
    return { inline: entry.contentRect.width, block: entry.contentRect.height };
  }

  return null;
}

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const size = readBox(entry, 'border');
    if (!size) continue;

    // 只在这里消费归一化尺寸,避免把兼容判断散落到业务组件。
    entry.target.style.setProperty('--box-inline', `${size.inline}px`);
  }
});

observer.observe(document.querySelector('.panel'), { box: 'border-box' });

记忆这套配方就够用了:先问组件要内容盒还是边框盒,再在 Entry 中优先读取对应数组的第一个逻辑尺寸对象,最后保留 contentRect 的明确回退。尺寸来源说清楚,ResizeObserver 就不会再变成“数字拿到了,但不知道它代表哪一圈”的黑盒。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
前端 BroadcastChannel 怎样同步不同标签页状态前端 BroadcastChannel 怎样同步不同标签页状态
上一篇
前端 BroadcastChannel 怎样同步不同标签页状态
前端 addEventListener 怎样用 once 自动移除一次性监听
下一篇
前端 addEventListener 怎样用 once 自动移除一次性监听
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    552次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    604次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    631次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    585次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    410次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码