跳到主要内容

CSS 盒模型

问题

什么是 CSS 盒模型?标准盒模型和 IE 盒模型有什么区别?margin 塌陷和合并分别是什么?

答案

盒模型概述

CSS 盒模型(Box Model)是 CSS 布局的基础概念。每个 HTML 元素都可以看作一个矩形盒子,由内到外依次由四部分组成:

┌─────────────────────────────────────┐
│ margin │
│ ┌───────────────────────────────┐ │
│ │ border │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ padding │ │ │
│ │ │ ┌───────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ └───────────────┘ │ │ │
│ │ └───────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
区域说明
content内容区域,放置文本、图片等实际内容
padding内边距,内容与边框之间的空间
border边框,包围内边距和内容的线条
margin外边距,元素与其他元素之间的空间

标准盒模型 vs IE 盒模型

两种盒模型的核心区别在于 widthheight 的计算范围不同

特性标准盒模型 (content-box)IE 盒模型 (border-box)
width 包含仅 contentcontent + padding + border
height 包含仅 contentcontent + padding + border
实际占用宽度width + padding + borderwidth(padding、border 被包含在内)
默认行为CSS 标准默认值IE 5.5 怪异模式默认值

标准盒模型(content-box)

标准盒模型
.box {
box-sizing: content-box; /* 默认值 */
width: 200px;
padding: 20px;
border: 5px solid #333;
}
/* 实际占用宽度 = 200 + 20*2 + 5*2 = 250px */

IE 盒模型(border-box)

IE 盒模型(推荐)
.box {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid #333;
}
/* 实际占用宽度 = 200px(content 区域被压缩为 150px) */
最佳实践

现代开发中通常使用全局 border-box,它更符合直觉 —— 设置 width: 200px 就是 200px:

全局 border-box 重置
*,
*::before,
*::after {
box-sizing: border-box;
}

几乎所有 CSS 框架(Tailwind CSS、Bootstrap)都采用此方案。

margin 合并(Margin Collapsing)

当两个垂直方向的 margin 相邻时,它们不会叠加,而是取较大值合并为一个。这种现象仅发生在块级元素垂直方向

三种触发场景

1. 相邻兄弟元素

相邻兄弟 margin 合并
.box-a {
margin-bottom: 30px;
}
.box-b {
margin-top: 20px;
}
/* A 和 B 之间的间距 = max(30, 20) = 30px,而不是 50px */

2. 父元素与第一个/最后一个子元素

父子 margin 合并
.parent {
margin-top: 0;
}
.child:first-child {
margin-top: 20px;
}
/* 子元素的 margin-top 会"穿透"父元素,表现为父元素有 20px 的 margin-top */

3. 空块级元素

空元素自身 margin 合并
.empty {
margin-top: 20px;
margin-bottom: 30px;
}
/* 如果该元素没有 border、padding、height、min-height、内容,
则自身的上下 margin 合并,最终占 30px */

margin 合并的计算规则

情况结果
两个正值较大值
一正一负相加(正 + 负)
两个负值绝对值较大的负值
/* 示例:一正一负 */
.a { margin-bottom: 30px; }
.b { margin-top: -10px; }
/* 间距 = 30 + (-10) = 20px */

阻止 margin 合并的方法

方法原理适用场景
创建 BFCBFC 内部与外部互不影响父子 margin 合并
设置 borderpadding隔开相邻 margin父子 margin 合并
使用 Flex/Grid 布局Flex/Grid 的子元素不发生合并所有场景
display: inline-block行内块不参与 margin 合并兄弟元素合并
overflow: hidden/auto创建 BFC父子 margin 合并
解决父子 margin 合并
/* 方法一:父元素设置 overflow */
.parent {
overflow: hidden; /* 创建 BFC */
}

/* 方法二:父元素设置 border/padding */
.parent {
padding-top: 1px; /* 即使 1px 也能阻止合并 */
}
/* 或 */
.parent {
border-top: 1px solid transparent;
}

/* 方法三:使用 Flex 布局 */
.parent {
display: flex;
flex-direction: column;
}

margin 塌陷

"margin 塌陷"通常特指父子元素的 margin 合并现象:子元素设置 margin-top 时,效果表现在父元素上而非子元素自身,就像子元素的 margin "塌陷"到了父元素身上。

margin 塌陷示例
<div class="parent">
<div class="child">Hello</div>
</div>

<style>
.parent {
background: #eee;
/* 没有 border、padding、overflow 等属性 */
}
.child {
margin-top: 50px;
/* 期望:child 距离 parent 顶部 50px */
/* 实际:parent 整体下移 50px(margin 穿透了父元素) */
}
</style>
margin 塌陷 vs margin 合并
  • margin 合并:是 CSS 规范定义的正常行为,涵盖所有垂直方向 margin 重叠的情况
  • margin 塌陷:是开发者对"父子 margin 合并"这一特定场景的通俗叫法,属于 margin 合并的子集

面试中两个概念经常混用,理解本质即可。

获取盒模型的尺寸

在 JavaScript 中获取元素尺寸的常用方式:

获取元素尺寸
const el = document.querySelector('.box') as HTMLElement;

// 1. 内容区 + padding + border(视觉可见区域)
console.log(el.offsetWidth, el.offsetHeight); // 包含 border

// 2. 内容区 + padding(不含 border 和滚动条)
console.log(el.clientWidth, el.clientHeight);

// 3. 实际内容的完整尺寸(包含溢出部分)
console.log(el.scrollWidth, el.scrollHeight);

// 4. 精确尺寸(可能包含小数,包含 transform 后的尺寸)
const rect = el.getBoundingClientRect();
console.log(rect.width, rect.height); // 包含 border
属性包含的区域是否整数是否受 transform 影响
offsetWidth/Heightcontent + padding + border + scrollbar是(四舍五入)
clientWidth/Heightcontent + padding(不含 scrollbar)是(四舍五入)
scrollWidth/Height实际内容(含溢出)
getBoundingClientRect()content + padding + border否(可能小数)

getComputedStyle 获取计算后样式

getComputedStyle 示例
const el = document.querySelector('.box') as HTMLElement;
const style = window.getComputedStyle(el);

console.log(style.width); // "200px"
console.log(style.paddingTop); // "20px"
console.log(style.marginTop); // "10px"
console.log(style.boxSizing); // "border-box"
注意

getComputedStyle 返回的 width 总是指 content 区域的宽度,无论 box-sizing 是什么值。如果是 border-box,返回的 width 值 = 设置的 width - padding - border。


常见面试问题

Q1: 介绍一下 CSS 盒模型

答案

CSS 盒模型描述了元素在页面中占据空间的方式,由四部分组成(从内到外):content(内容区)padding(内边距)border(边框)margin(外边距)

有两种盒模型:

  • 标准盒模型box-sizing: content-box):width/height 只包含 content,实际宽度 = width + padding + border
  • IE 盒模型box-sizing: border-box):width/height 包含 content + padding + border

现代开发推荐使用 border-box,因为更直观。

Q2: box-sizing 有哪些值?分别什么含义?

答案

含义
content-box(默认)width/height 只包含内容区,padding 和 border 额外计算
border-boxwidth/height 包含内容区 + padding + border
/* 两个 .box 视觉宽度都是 200px */
.box-standard {
box-sizing: content-box;
width: 160px; /* 160 + 15*2 + 5*2 = 200px */
padding: 15px;
border: 5px solid;
}
.box-border {
box-sizing: border-box;
width: 200px; /* 直接就是 200px */
padding: 15px;
border: 5px solid;
}

Q3: 什么是 margin 合并?怎么解决?

答案

垂直方向上相邻的 margin 会发生合并,取较大值而非叠加。有三种场景:

  1. 相邻兄弟元素的上下 margin
  2. 父元素与第一个/最后一个子元素的 margin
  3. 空块元素自身的上下 margin

解决方式:

  • 创建 BFC(overflow: hidden/display: flow-root
  • 设置 borderpadding 隔开
  • 使用 Flex/Grid 布局
  • 改为 inline-block

Q4: offsetWidthclientWidthscrollWidth 的区别?

答案

                 ┌───────────────────────────┐
offsetWidth → │ border + padding + content + scrollbar │
└───────────────────────────┘
┌───────────────────────────┐
clientWidth → │ padding + content(不含 scrollbar)│
└───────────────────────────┘
┌───────────────────────────┐
scrollWidth → │ 实际内容区(含溢出不可见部分)│
└───────────────────────────┘
  • offsetWidth = border + padding + content + scrollbar(元素完整视觉宽度)
  • clientWidth = padding + content - scrollbar(元素内部可用宽度)
  • scrollWidth = 实际内容宽度,包括溢出(overflow)的部分
  • getBoundingClientRect().widthoffsetWidth 类似,但返回浮点数且受 transform 影响

Q5: 为什么推荐全局使用 box-sizing: border-box

答案

  1. 更直观width: 300px 就是 300px,不需要手动减去 padding 和 border
  2. 布局更方便:百分比宽度 + padding 不会溢出父容器
  3. 一致性<input><textarea> 等表单元素浏览器默认值不一致,统一设置可避免 bug
  4. 行业标准:所有主流 CSS 框架(Bootstrap、Tailwind、Ant Design)都采用此方案
/* 经典重置 */
*, *::before, *::after {
box-sizing: border-box;
}

Q6: margin 设置百分比是相对于谁的?

答案

margin 的百分比值始终相对于包含块的宽度(width),包括 margin-topmargin-bottom

.parent {
width: 400px;
height: 800px;
}
.child {
margin-top: 10%; /* 40px(400 * 10%),而不是 800 * 10% = 80px */
margin-left: 10%; /* 40px */
}

这是因为 CSS 规范为了保证在水平和垂直方向上 margin 的百分比计算一致性,统一使用包含块的宽度作为参考。同理,padding 的百分比也是相对于包含块的宽度。

Q7: 如何用 padding 实现一个固定宽高比的容器?

答案

利用 padding 百分比相对于父元素宽度计算的特性:

传统方案(padding hack)
.aspect-ratio-box {
width: 100%;
padding-top: 56.25%; /* 16:9 = 9/16 = 0.5625 */
position: relative;
height: 0;
}
.aspect-ratio-box > * {
position: absolute;
inset: 0;
}
现代方案(推荐)
.aspect-ratio-box {
aspect-ratio: 16 / 9;
width: 100%;
}

Q8: outlineborder 有什么区别?

答案

特性borderoutline
是否占据空间,参与盒模型计算,不影响布局
是否可以单独设置某一边可以(border-top 等)不可以
是否可以设置圆角是(border-radius是(outline + border-radius
是否影响元素尺寸
典型用途装饰性边框焦点指示(:focus
.btn:focus-visible {
outline: 2px solid blue;
outline-offset: 2px; /* outline 可以设置偏移 */
}

Q9: box-shadow 会影响盒模型布局吗?

答案

不会。box-shadow 不占据空间,不影响布局。它只是视觉上的装饰效果,类似于 outline。如果需要阴影占据空间,可以通过等量的 margin 来补偿。

.card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
/* 阴影不占空间,相邻元素不会被推开 */
}

Q10: 行内元素的盒模型有什么特殊之处?

答案

行内元素(display: inline,如 <span><a>)的盒模型与块级元素不同:

特性行内元素块级元素
width/height无效有效
水平方向 padding有效有效
垂直方向 padding有效但不影响布局(只影响背景区域)有效且影响布局
水平方向 margin有效有效
垂直方向 margin无效有效
border有效但垂直方向不影响布局有效
span {
padding: 20px; /* 水平方向正常,垂直方向不推开相邻行 */
margin: 20px; /* 水平方向正常,垂直方向无效 */
border: 2px solid; /* 同 padding */
}

要让行内元素的垂直方向属性生效,需改为 display: inline-blockdisplay: block

相关链接