盒模型与BFC

8 分钟

盒模型基础

每个 HTML 元素在渲染时都会生成一个矩形盒子,由内到外依次是:content → padding → border → margin。这四层构成了 CSS 盒模型的核心。

两种盒模型的区别在于 width / height 的计算范围不同。

标准盒模型 vs IE 盒模型

标准盒模型(content-box)

widthheight 仅包含 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)

widthheight 包含 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:

属性触发值
overflowvisible(如 hiddenautoscroll
floatnone(如 leftright
positionabsolutefixed
displayinline-blockflexinline-flexgridinline-gridtable-cellflow-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: hiddendisplay: 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 问题
触发 BFCflow-root > overflow: hidden > float/position/flex