盒模型与BFC
盒模型基础
每个 HTML 元素在渲染时都会生成一个矩形盒子,由内到外依次是:content → padding → border → margin。这四层构成了 CSS 盒模型的核心。
两种盒模型的区别在于 width / height 的计算范围不同。
标准盒模型 vs IE 盒模型
标准盒模型(content-box)
width 和 height 仅包含 content 区域。padding 和 border 会额外撑大元素的实际占位。
.box {
box-sizing: content-box; /* 默认值 */
width: 200px;
padding: 20px;
border: 5px solid #333;
}
/* 实际占位宽度 = 200 + 20*2 + 5*2 = 250px */
IE 盒模型(border-box)
width 和 height 包含 content + padding + border。设定多少宽度,元素就占多少宽度,padding 和 border 向内挤压 content。
.box {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid #333;
}
/* 实际占位宽度 = 200px(content 被压缩为 150px) */
工程实践中的选择
绝大多数项目会在全局重置中统一使用 border-box,避免布局计算时反复做加法:
*,
*::before,
*::after {
box-sizing: border-box;
}
这也是 Normalize.css、Tailwind CSS 等方案的默认做法。
margin 塌陷与合并
垂直方向 margin 合并
相邻兄弟元素的垂直 margin 会取较大值而非叠加:
.a { margin-bottom: 30px; }
.b { margin-top: 20px; }
/* 两者间距 = max(30, 20) = 30px,而非 50px */
父子 margin 塌陷
子元素的 margin-top 会"穿透"父元素,与父元素的 margin 合并,导致父元素整体下移而非子元素在父元素内部产生间距。
<div class="parent">
<div class="child" style="margin-top: 20px;"></div>
</div>
此时 .parent 会向下偏移 20px,而非 .child 在父容器内部产生 20px 的顶部间距。
解决方式:给父元素触发 BFC、添加 padding-top、添加 border-top、或使用 overflow: hidden。
BFC 是什么
BFC(Block Formatting Context,块级格式化上下文)是页面中一块独立的渲染区域。内部的布局不会影响外部元素,外部的布局也不会影响内部元素。
可以把 BFC 理解为一个"结界"——结界内部的 margin、浮动等行为被隔离在内部,不会泄漏到外面。
BFC 触发条件
满足以下任一条件即可创建 BFC:
| 属性 | 触发值 |
|---|---|
overflow | 非 visible(如 hidden、auto、scroll) |
float | 非 none(如 left、right) |
position | absolute 或 fixed |
display | inline-block、flex、inline-flex、grid、inline-grid、table-cell、flow-root |
| 根元素 | <html> 本身就是一个 BFC |
其中 display: flow-root 是专门为创建 BFC 设计的,没有任何副作用,是目前最推荐的方式。
BFC 的核心特性
1. 内部 margin 不会与外部合并
BFC 形成隔离边界,阻止其内部子元素的 margin 与外部元素发生合并。
.parent {
display: flow-root; /* 触发 BFC */
}
.child {
margin-top: 20px; /* 不再穿透到 parent 外部 */
}
2. 可以包含浮动元素(清除浮动)
普通容器无法感知浮动子元素的高度,导致高度塌陷。触发 BFC 后,容器会将浮动子元素纳入高度计算。
.clearfix {
display: flow-root; /* 或 overflow: hidden */
}
<div class="clearfix">
<div style="float: left; width: 100px; height: 100px;"></div>
<!-- 父容器高度不再为 0 -->
</div>
3. BFC 区域不会与浮动元素重叠
BFC 元素会自动避开相邻浮动元素的占位区域,这是实现两栏/三栏自适应布局的原理之一。
.sidebar {
float: left;
width: 200px;
}
.main {
overflow: hidden; /* 触发 BFC,不被 sidebar 覆盖 */
}
BFC 实际应用场景
场景一:解决父子 margin 穿透
.wrapper {
display: flow-root;
}
.inner {
margin-top: 40px; /* 正常生效,不穿透 */
}
场景二:清除浮动导致的高度塌陷
.container {
display: flow-root;
}
.float-item {
float: left;
width: 50%;
}
场景三:两栏自适应布局
.left {
float: left;
width: 240px;
background: #f0f0f0;
}
.right {
overflow: hidden; /* BFC 不与浮动重叠 */
background: #e0e0e0;
}
场景四:阻止相邻元素 margin 合并
将其中一个元素包裹在 BFC 容器中,即可阻止合并行为:
<div class="box-a" style="margin-bottom: 30px;"></div>
<div style="display: flow-root;">
<div class="box-b" style="margin-top: 20px;"></div>
</div>
<!-- 间距 = 30 + 20 = 50px -->
面试高频追问
Q:overflow: hidden 和 display: flow-root 都能触发 BFC,区别是什么?
overflow: hidden 会裁剪超出容器的内容(如定位元素、长文本),可能产生副作用。display: flow-root 专为创建无副作用的 BFC 而生,不影响内容溢出的显示,是更优解。兼容性方面,flow-root 在 IE 中不支持,需根据项目浏览器兼容要求选择。
Q:BFC 和 IFC(Inline Formatting Context)的区别?
BFC 管理块级盒子的垂直排列和 margin 行为;IFC 管理行内盒子的水平排列、行高计算、vertical-align 对齐。两者是不同的格式化上下文,作用对象和布局规则不同。
Q:为什么 display: flex 的子元素不会发生 margin 合并?
Flex 容器本身会创建 BFC,且其内部使用 Flex Formatting Context 进行布局,flex item 之间的 margin 不参与常规的块级 margin 合并规则。
Q:如何不触发 BFC 也能清除浮动?
经典的 clearfix hack 使用伪元素:
.clearfix::after {
content: "";
display: block;
clear: both;
}
这种方式不需要改变容器自身的 formatting context,而是通过在末尾插入一个清除浮动的块级伪元素来撑开父容器高度。
总结
| 概念 | 核心要点 |
|---|---|
| 标准盒模型 | width = content,实际宽度需加上 padding + border |
| IE 盒模型 | width = content + padding + border,所见即所得 |
| margin 合并 | 垂直方向相邻 margin 取最大值,父子间会穿透 |
| BFC | 独立渲染区域,隔离内部布局,解决浮动/margin 问题 |
| 触发 BFC | flow-root > overflow: hidden > float/position/flex |