盒模型与布局基础
为什么盒模型是 CSS 的物理定律
CSS 中的每个元素都是一个矩形盒子。无论你使用 Flexbox、Grid 还是 Float,底层都遵循盒模型的规则。理解盒模型不仅是"知道 padding 和 margin 的区别",而是掌握浏览器如何将抽象的样式声明转换为屏幕上的像素。
Margin 折叠、BFC(Block Formatting Context)、box-sizing 的行为差异——这些都是实际开发中高频出现的布局问题根源。
面试定位:盒模型是 CSS 布局面试的基石。面试官通过 margin 折叠的规则、BFC 的触发条件与实际用途、
box-sizing差异等问题,判断候选人对 CSS 底层机制的掌握程度。
盒模型四层结构
Content → Padding → Border → Margin
┌──────────────────────── margin ────────────────────────┐
│ ┌──────────────────── border ──────────────────────┐ │
│ │ ┌────────────── padding ──────────────────┐ │ │
│ │ │ ┌──────── content ────────────┐ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ width × height │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
| 层 | 作用 | 背景 | 是否可点击 |
|---|---|---|---|
| Content | 容纳文本、子元素等内容 | 受 background 影响 |
是 |
| Padding | 内容与边框间的内部留白 | 受 background 影响 |
是 |
| Border | 盒子的可见边界 | 独立 border-color |
是 |
| Margin | 盒子与外部元素的间距 | 始终透明 | 否 |
box-sizing: content-box vs border-box
这个属性决定了 width 和 height 的计算范围:
/* content-box(默认值) */
.box-content {
box-sizing: content-box;
width: 200px;
padding: 20px;
border: 2px solid;
/* 实际占用宽度 = 200 + 20*2 + 2*2 = 244px */
}
/* border-box */
.box-border {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 2px solid;
/* 实际占用宽度 = 200px(padding 和 border 包含在内) */
/* content 宽度 = 200 - 20*2 - 2*2 = 156px */
}
工程标准:现代项目几乎都使用全局 border-box:
*,
*::before,
*::after {
box-sizing: border-box;
}
这是因为 border-box 让元素的尺寸计算更符合直觉——当你说"这个盒子宽 200px"时,它就是 200px,不会因为 padding 而膨胀。
Margin 折叠(Margin Collapsing)
核心规则
**只有垂直方向(block 方向)**的相邻 margin 会折叠,水平方向不会:
/* 两个相邻块元素 */
.box-a { margin-bottom: 30px; }
.box-b { margin-top: 20px; }
/* 实际间距 = max(30, 20) = 30px,不是 50px */
三种折叠场景
1. 相邻兄弟元素
<div class="a" style="margin-bottom: 30px">A</div>
<div class="b" style="margin-top: 20px">B</div>
<!-- A 和 B 之间的间距是 30px -->
2. 父元素与第一个/最后一个子元素
<div class="parent" style="margin-top: 30px">
<div class="child" style="margin-top: 20px">Child</div>
</div>
<!-- parent 的上 margin 和 child 的上 margin 折叠 -->
<!-- 结果:parent 上方 30px 间距,child 紧贴 parent 顶部 -->
3. 空的块元素(自身的 top 和 bottom margin 折叠)
<div style="margin-top: 30px; margin-bottom: 20px"></div>
<!-- 这个空元素只贡献 30px 的垂直空间 -->
阻止折叠的条件
以下任一条件可阻止 margin 折叠:
| 条件 | 原因 |
|---|---|
父元素有 padding-top/bottom |
打破了"相邻"关系 |
父元素有 border-top/bottom |
同上 |
| 父元素创建了新的 BFC | BFC 边界阻止折叠穿透 |
元素设置了 overflow: hidden/auto/scroll |
创建了 BFC |
| 元素是 Flex/Grid 容器的直接子项 | Flex/Grid 格式化上下文不折叠 margin |
| 浮动元素或绝对定位元素 | 脱离了正常文档流 |
/* 阻止父子 margin 折叠的几种方式 */
.parent-v1 { overflow: hidden; } /* 创建 BFC */
.parent-v2 { display: flow-root; } /* 创建 BFC(推荐) */
.parent-v3 { padding-top: 1px; } /* 打破相邻 */
.parent-v4 { border-top: 1px solid transparent; } /* 打破相邻 */
Block Formatting Context(BFC)
什么是 BFC
BFC 是一个独立的布局区域,内部元素的布局不影响外部,外部也不影响内部。它是理解 CSS 布局的关键概念之一。
创建 BFC 的条件
| 方式 | CSS 声明 | 副作用 |
|---|---|---|
display: flow-root |
专为创建 BFC 设计 | 无 ✅ |
overflow 非 visible |
overflow: hidden/auto/scroll |
可能裁剪内容 |
| 浮动元素 | float: left/right |
改变布局流 |
| 绝对/固定定位 | position: absolute/fixed |
脱离文档流 |
| Flex/Grid 子项 | 父元素为 flex/grid 容器 | — |
| 行内块 | display: inline-block |
改变外部显示类型 |
| 表格单元格 | display: table-cell |
— |
contain: layout/content/paint |
CSS Containment | 可能影响子树渲染 |
BFC 的三大应用
1. 清除浮动(Clearfix)
/* 现代写法:display: flow-root */
.container {
display: flow-root; /* 包裹浮动子元素 */
}
/* 传统 clearfix hack */
.clearfix::after {
content: "";
display: block;
clear: both;
}
2. 阻止 margin 折叠
.parent {
display: flow-root; /* 创建 BFC,子元素的 margin 不会穿透 */
}
3. 阻止文本环绕浮动
.float-left { float: left; width: 200px; }
.text-content {
overflow: hidden; /* 创建 BFC,不会环绕浮动元素 */
}
Visual Formatting Model
display 的双值语法
CSS Display Module Level 3 引入了双值语法,将 display 拆分为外部显示类型和内部显示类型:
| 旧值 | 新双值语法 | 外部 | 内部 |
|---|---|---|---|
block |
block flow |
block | flow |
inline |
inline flow |
inline | flow |
inline-block |
inline flow-root |
inline | flow-root |
flex |
block flex |
block | flex |
inline-flex |
inline flex |
inline | flex |
grid |
block grid |
block | grid |
inline-grid |
inline grid |
inline | grid |
/* 等价写法 */
.container { display: flex; }
.container { display: block flex; } /* 外部 block,内部 flex */
.tag { display: inline-flex; }
.tag { display: inline flex; } /* 外部 inline,内部 flex */
理解价值:双值语法揭示了 display 实际控制两件事——元素如何参与外部布局(block 或 inline),以及如何格式化内部子元素(flow、flex、grid)。
块级元素 vs 行内元素
| 特性 | Block | Inline | Inline-Block |
|---|---|---|---|
| 是否换行 | 是(独占一行) | 否 | 否 |
| width/height | 有效 | 无效 | 有效 |
| 上下 margin/padding | 有效 | margin 无效,padding 不影响行高 | 有效 |
| 默认宽度 | 填满父容器 | 内容宽度 | 内容宽度 |
/* 行内元素的 padding 陷阱 */
span {
padding: 20px;
background: yellow;
/* padding 会渲染(背景可见),但不影响行高和周围元素的布局 */
/* 上下 padding 会与相邻行重叠 */
}
Logical Properties(逻辑属性)
从物理方向到逻辑方向
传统 CSS 使用物理方向(top、right、bottom、left),但在国际化场景中(RTL 阿拉伯语、竖排中文),物理方向会失效:
| 物理属性 | 逻辑属性(水平书写模式) | 映射 |
|---|---|---|
margin-top |
margin-block-start |
块轴起始 |
margin-bottom |
margin-block-end |
块轴结束 |
margin-left |
margin-inline-start |
行内轴起始 |
margin-right |
margin-inline-end |
行内轴结束 |
width |
inline-size |
行内轴尺寸 |
height |
block-size |
块轴尺寸 |
top |
inset-block-start |
— |
border-radius: 8px 0 0 8px |
border-start-start-radius: 8px; border-end-start-radius: 8px |
— |
/* 传统写法——RTL 语言中方向错误 */
.sidebar {
margin-left: 20px;
padding-right: 16px;
border-left: 2px solid blue;
}
/* 逻辑属性——自动适配书写方向 */
.sidebar {
margin-inline-start: 20px;
padding-inline-end: 16px;
border-inline-start: 2px solid blue;
}
简写属性
/* margin-block / margin-inline */
.box {
margin-block: 20px 10px; /* block-start: 20px, block-end: 10px */
margin-inline: auto; /* 水平居中 */
padding-block: 1rem; /* 上下 padding 相同 */
padding-inline: 2rem; /* 左右 padding 相同 */
}
/* inset 简写 */
.overlay {
position: absolute;
inset: 0; /* top: 0; right: 0; bottom: 0; left: 0 */
}
.drawer {
position: fixed;
inset-block: 0; /* 全高 */
inset-inline-start: 0; /* 起始侧 */
inline-size: 300px; /* 宽度 */
}
尺寸计算与内在尺寸
Intrinsic Sizing(内在尺寸关键字)
.box {
/* min-content: 内容不换行的最小宽度(最长单词的宽度) */
width: min-content;
/* max-content: 内容完全不换行的宽度 */
width: max-content;
/* fit-content: 类似 max-content 但不超过可用空间 */
width: fit-content;
/* fit-content(): 带上限的 fit-content */
width: fit-content(300px);
}
应用场景:
/* 按钮宽度自适应文本内容 */
.btn {
width: fit-content;
min-width: 80px;
}
/* 图片说明文字不超过图片宽度 */
figure {
width: min-content;
}
figure img {
max-width: 100%;
}
/* 导航标签:内容宽度 + 固定 padding */
.nav-tab {
width: max-content;
padding: 8px 16px;
white-space: nowrap;
}
calc() 与数学函数
.sidebar {
width: calc(100% - 250px);
min-height: calc(100vh - 60px);
}
/* min() / max() / clamp() */
.container {
width: min(1200px, 100% - 2rem); /* 响应式容器 */
padding: clamp(1rem, 3vw, 3rem); /* 流式 padding */
}
/* 嵌套运算 */
.grid-item {
width: calc((100% - 3 * var(--gap)) / 4);
}
实战场景
场景一:全局 Reset 最佳实践
/* Modern CSS Reset */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
body {
min-height: 100vh;
line-height: 1.5;
}
img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}
input, button, textarea, select {
font: inherit;
}
场景二:等高卡片布局中的 margin 问题
/* 问题:Flex 容器中卡片的 margin 不折叠 */
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 1rem; /* 使用 gap 替代 margin */
}
.card {
/* 不需要 margin,gap 处理间距 */
flex: 1 1 calc(33.333% - 1rem);
display: flex;
flex-direction: column;
}
.card-body {
flex: 1; /* 内容区撑满,实现等高 */
}
面试高频问题
Q: content-box 和 border-box 的区别?为什么推荐 border-box?
回答要点:content-box 的 width/height 只包含内容区,padding 和 border 会额外增加盒子的实际尺寸。border-box 的 width/height 包含了 content + padding + border。推荐 border-box 因为它更符合设计直觉——当设计稿标注"宽 200px"时,border-box 能确保元素就是 200px,不会因 padding 膨胀。
Q: 什么是 Margin 折叠?如何避免?
回答要点:只发生在垂直方向的相邻 block margin 之间(包括兄弟元素、父子元素、空元素自身)。折叠后取较大值。阻止方式:创建 BFC(display: flow-root)、添加 padding/border 打破相邻关系、使用 Flex/Grid 布局。
Q: 什么是 BFC?有什么实际用途?
回答要点:BFC 是独立的布局区域,内部布局不影响外部。创建 BFC 的推荐方式是 display: flow-root。三大用途:清除浮动(包裹浮动子元素)、阻止 margin 折叠、阻止文本环绕浮动。
Q: 逻辑属性(Logical Properties)解决什么问题?
回答要点:传统物理属性(top/left/right/bottom)在 RTL 语言或竖排文本中方向错误。逻辑属性使用 block-start/end、inline-start/end 表示方向,自动适配 writing-mode 和 direction,是国际化应用的基础设施。