在构建网页布局时,你是否遇到过这样的“幽灵bug”:明明给两个左右并排的盒子各自设置了 width: 50%,结果仅仅是因为给其中一个盒子加了点 padding 或是 border,第二个盒子就直接掉到下一行去了?
又或者,你按照设计稿完美敲定了盒子的 width 和 height,结果加上边距后,整个盒子变得比设计稿大了一圈?
要搞清楚这是为什么,并在开发中彻底规避这类计算灾难,我们需要深入探讨 CSS 的基石概念:盒模型(Box Model) 以及它的总开关 box-sizing。
在浏览器眼里,网页上的任何一个元素(无论是 <div> 还是 <span>),都是一个矩形的“盒子”。这个盒子由内到外由四个部分组成:
虽然每个元素都有这四个部分,但在 CSS 早期,关于“当你设置了 width 和 height 时,到底是在设置谁的尺寸”这个问题,浏览器厂商之间产生过巨大的分歧。
box-sizing 的两种世界观为了解决上述分歧,CSS3 引入了 box-sizing 属性,它允许开发者主动选择采取哪种方式来计算盒子的尺寸。它主要有两个核心值:
content-box(标准盒模型 / W3C 标准)这是浏览器的默认行为。在这个模型下,你在 CSS 中写的 width 和 height,仅仅是内容(Content)的宽高。
width + 左右 padding + 左右 border。padding 或 border,你都不得不手动去减小 width 以维持盒子的总大小。border-box(IE 盒模型 / 怪异盒模型)这是现代前端工程师最推崇的模型。在这个模型下,你在 CSS 中写的 width 和 height,是包含了内容、内边距和边框的“总尺寸”。
width。padding 或 border,盒子的总宽高雷打不动。浏览器会聪明地自动压缩内部内容区域(Content)的空间,来塞下你新增的边距和边框。33.33% 后,任你加粗边框,它依然正好占三分之一)。假设我们在 CSS 里写了这样一段样式:
box-sizing: content-box):
这个盒子在屏幕上实际占据的宽度为 130px (100 + 10 * 2 + 5 * 2)。内部内容宽度维持 100px 没变,外部被硬生生撑大了。box-sizing: border-box:
这个盒子在屏幕上实际占据的宽度始终是 100px。浏览器会自动将内部真正能放文字内容的宽度压缩为 70px (100 - 10 * 2 - 5 * 2),盒子外观尺寸丝毫不受影响。因为 border-box 的计算方式实在太符合人类直觉了,所以当今几乎所有的主流前端框架(如 Bootstrap、Tailwind CSS)和正规商业项目,都会在最底层的全局 CSS (Reset CSS) 中强行将所有元素扭转为 border-box 模型:
加上这段代码后,你以后写 width: 200px,它在屏幕上就绝对是 200px 宽,再也不用担心调个内边距就把布局震碎的问题了。
在实际交互开发中,我们经常需要用 JS 动态获取元素的尺寸。由于盒模型结构的复杂性,DOM 提供了多种不同的 API:
dom.style.width/height
<div style="width: 100px">)设置的值。<style> 标签里的尺寸,且返回的是带单位的字符串(如 "100px")。dom.clientWidth/clientHeight
dom.offsetWidth/offsetHeight
dom.getBoundingClientRect().width/height
transform: scale()) 后的渲染几何尺寸。而且精度更高(可能带有小数)。window.getComputedStyle(dom).width/height
box-sizing 的设置。在盒模型最外围的 Margin,有着自己特殊的脾气:Margin 塌陷(合并)。 在标准文档流中,垂直方向上相邻的两个块级元素,它们的上下 Margin 不会相加,而是会发生合并,最终取两者中的最大值。
overflow: hidden、转换为 display: flex 或 position: absolute 等。Q1:说说你对 CSS 盒模型的理解?box-sizing 属性有哪些值及含义?
回答思路:
box-sizing:
content-box,width 只管内容区,加 padding/border 会撑大盒子。border-box,width 管总体积(包含 padding 和 border)。只会向内挤压内容区,外部大小不变。* { box-sizing: border-box; } 全局重置,减轻心智负担。Q2:如何用 JS 获取一个元素的实际渲染高度?clientHeight 和 offsetHeight 有什么区别?
回答思路:
offsetHeight 或 getBoundingClientRect().height。clientHeight 只是“内部空间”(Content + Padding),而 offsetHeight 才是完整的“物理可见空间”(多加了 Border 和滚动条)。如果涉及动画缩放,最好用 getBoundingClientRect()。Q3:什么是 Margin 塌陷?怎么解决?
回答思路:
overflow: hidden、改变 display 为 flex 或 inline-block 触发 BFC 即可解决。