第20章 格式化上下文
第二十章:格式化上下文
格式化上下文(Formatting Context)是 CSS 布局中一个相对高级的概念。理解它,你就能解释很多"奇怪"的布局现象——比如为什么父元素高度塌陷、为什么 margin 会重叠、为什么 overflow:hidden 能清除浮动。这些问题的答案,都藏在格式化上下文中。
20.1 BFC 块级格式化上下文
20.1.1 什么是 BFC——Block Formatting Context,块级元素参与的区域,形成独立的渲染区域
BFC(Block Formatting Context,块级格式化上下文)是 CSS 布局中最重要的概念之一。简单来说,BFC 是一个独立的渲染区域,在这个区域内的元素按照块级布局规则排列,但这个区域本身与外部是隔离的。
什么是 BFC?
想象一下一个透明的玻璃鱼缸(格式化上下文)。鱼缸里有自己的水流(内部布局),鱼缸里的水不会流到外面去,但鱼缸里的鱼(子元素)可以自由游动。BFC 就像是这个玻璃鱼缸——它有自己的内部布局规则,但不会影响外部元素。
1
2
3
4
5
6
7
| /* BFC 的特点 */
/* 形成 BFC 的元素 */
.bfc-container {
display: flow-root; /* 创建一个 BFC */
/* 或者使用其他方式 */
}
|
BFC 的形成条件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| /* 以下属性会让元素形成 BFC */
/* 1. overflow 不为 visible */
.overflow-bfc {
overflow: hidden; /* hidden/auto/scroll 均可 */
}
/* 2. display 为 flow-root */
.flow-root-bfc {
display: flow-root; /* 专门用来创建 BFC */
}
/* 3. display 为 inline-block */
.inline-block-bfc {
display: inline-block;
}
/* 4. position 为 absolute 或 fixed */
.position-bfc {
position: absolute;
/* 或 position: fixed */
}
/* 5. flex 项目、grid 项目(注意是"项目/子元素",不是容器本身)
项目的内容会形成独立的格式化上下文 */
.flex-item-bfc {
/* 只要它是 flex 容器的子元素,就自动享有隔离效果 */
flex: 1;
}
/* 6. 多列布局 */
.multi-column-bfc {
column-count: 2; /* 形成 BFC */
}
/* 7. display: table-cell(表格单元格) */
.table-bfc {
display: table-cell; /* 单元格会形成 BFC */
}
/* 8. float 不为 none */
.float-bfc {
float: left; /* 触发 BFC */
}
|
触发 BFC 的方式有很多种,每种都有不同的适用场景。
先纠正一个流传很广的说法:display: flex / display: grid 的"容器"建立的是 flex 格式化上下文和 grid 格式化上下文,不是 BFC。真正会建立独立格式化上下文的是它们的项目(item)。之所以网上的文章常把两者混为一谈,是因为在"包住浮动"“阻止 margin 折叠"这些表现上它们的效果几乎一样。下文会分开说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| /* 触发 BFC 的各种方式 */
/* 方式1:overflow 不为 visible */
.method-overflow {
overflow: hidden; /* 推荐用 hidden 或 auto */
/* 注意:visible 会让 BFC 失效 */
/* 副作用:hidden 会裁掉溢出内容;auto 可能突然冒出滚动条 */
}
/* 方式2:display: flow-root */
.method-flow-root {
display: flow-root; /* 最语义化的方式 */
}
/* 方式3:display: inline-block */
.method-inline-block {
display: inline-block; /* 会产生一个 BFC */
}
/* 方式4:position: absolute / fixed */
.method-position {
position: absolute;
/* 或 position: fixed; */
}
/* 方式5:display: flex 或 grid */
/* flex 容器的直接子元素(flex item)会形成独立格式化上下文 */
.method-flex-item {
/* 父元素 display: flex;这个子元素内部自成一体 */
flex: 1;
}
/* grid 项目同理 */
.method-grid-item {
/* 父元素 display: grid;这个子元素内部自成一体 */
}
/* 方式6:column-count 或 column-width */
.method-columns {
column-count: 2; /* 多列容器形成 BFC */
}
/* 方式7:display: table-cell(单元格),注意不是 display: table */
.method-table {
display: table-cell; /* 单元格形成 BFC */
}
/* 方式8:float 不为 none */
.method-float {
float: left; /* 触发 BFC */
}
/* 方式9:contain 属性(现代写法,性能友好) */
.method-contain {
contain: layout; /* layout / paint / content 都会创建独立的格式化上下文,也常用于性能优化 */
}
|
补充说明:display: table 到底算不算 BFC?
严格来说不算。display: table 生成的是表格盒子,建立的是表格格式化上下文。但它"看起来能包住浮动、能隔断 margin 折叠”,是因为它产生的匿名单元格(display: table-cell)建立了 BFC。所以:
- 想从原理上讲清楚 → 记住"触发 BFC 的是单元格,不是表格本身"
- 只是想解决问题 → 用
display: flow-root 更直接,也不必记住这些弯弯绕
20.1.3 BFC 的特性——包裹浮动元素(不塌陷)、阻止 margin 折叠、防止内部元素被外部浮动环绕
BFC 有三个非常重要的特性,这些特性解释了为什么很多"奇怪"的 CSS 布局问题都可以用 BFC 来解决。
特性1:包裹浮动元素(不塌陷)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| /* BFC 包裹浮动元素,解决父元素高度塌陷问题 */
/* 问题:没有触发 BFC 的父元素,高度塌陷 */
.parent-collapsed {
border: 2px solid red;
}
.parent-collapsed .float-child {
float: left; /* 浮动元素脱离文档流 */
width: 100px;
height: 100px;
background-color: #3498db;
}
/* 解决方案:让父元素触发 BFC */
.parent-with-bfc {
display: flow-root; /* 触发 BFC */
border: 2px solid green;
}
.parent-with-bfc .float-child {
float: left;
width: 100px;
height: 100px;
background-color: #2ecc71;
}
|
1
2
3
4
5
6
7
8
9
10
11
| <!-- 问题:父元素高度塌陷 -->
<div class="parent-collapsed">
<div class="float-child">浮动元素</div>
</div>
<!-- 父元素高度为0,背景边框显示不正确 -->
<!-- 解决:父元素触发 BFC -->
<div class="parent-with-bfc">
<div class="float-child">浮动元素</div>
</div>
<!-- 父元素高度被撑开 -->
|
特性2:阻止 margin 折叠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| /* BFC 阻止 margin 折叠 */
/* 问题:上下 margin 折叠 */
.margin-collapse {
background-color: #f8f9fa;
padding: 20px;
}
.margin-collapse .top-box {
margin-bottom: 20px;
background-color: #3498db;
}
.margin-collapse .bottom-box {
margin-top: 30px;
background-color: #2ecc71;
}
/* 解决方案:让一个元素触发 BFC */
.margin-collapse .bottom-box {
display: flow-root; /* 触发 BFC,阻止折叠 */
}
|
1
2
3
4
5
6
7
| <div class="margin-collapse">
<div class="top-box">上盒子,margin-bottom: 20px</div>
<!-- 间距是 30px,不是 50px(折叠了)-->
<div class="bottom-box">下盒子,margin-top: 30px</div>
<!-- 解决方案:给下盒子触发 BFC,间距变成 50px -->
</div>
|
特性3:阻挡外部元素环绕(不重叠)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| /* BFC 阻挡外部元素环绕 */
.float-box {
float: left;
width: 100px;
height: 100px;
background-color: #3498db;
margin-right: 20px;
}
.normal-text {
/* 普通文本会环绕浮动元素 */
/* 但如果触发了 BFC,文本会被阻挡 */
}
.bfc-text {
display: flow-root; /* 触发 BFC */
/* BFC 内部的元素不会被外部浮动元素环绕 */
}
|
💡 小技巧:BFC 的这三个特性非常实用——overflow: hidden 或 display: flow-root 可以同时解决浮动塌陷、margin 折叠和文本环绕三个问题。
20.2 IFC 行内格式化上下文
20.2.1 什么是 IFC——Inline Formatting Context,行内元素参与的区域
IFC(Inline Formatting Context,行内格式化上下文)是行内元素参与布局的区域。在 IFC 中,行内元素水平排列,形成一个"行框"(Line Box)。
什么是 IFC?
IFC 是行内元素排列的区域。当一行内容放不下时,会自动换行形成新的 Line Box。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| /* IFC 的特点 */
/* IFC 由行内元素组成 */
.inline-elements {
/* 行内元素会在 IFC 中排列 */
}
.inline-elements span {
/* 每个 span 都在 IFC 中 */
}
.inline-elements strong {
/* strong 也在同一个 IFC 中 */
}
|
IFC 的基本概念:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| /* IFC 由一个或多个行框(Line Box)组成 */
/* 行框的高度由最高元素的 ascent 和最低元素的 descent 决定 */
/* 示例 */
.ifc-container {
font-size: 16px;
line-height: 1.5;
/* 行框高度 ≈ 16 * 1.5 = 24px */
}
.ifc-container .tall {
font-size: 24px; /* 更高的文字 */
/* 会影响行框高度 */
}
|
20.2.2 行框高度计算——由最高元素的 ascent 和最低元素的 descent 决定,line-height 和 vertical-align 影响行框高度
IFC 中行框(Line Box)的高度是由其内部所有行内元素共同决定的。
先纠正一个常见的简化说法:line-height 并不等于 ascent + descent。字体的 ascent 和 descent 是字体自身的度量(由字体文件决定),而 line-height 是你可以任意指定的值。当 line-height 大于字体的 ascent + descent 时,多出来的部分叫 leading(行距),会被平均分配到文字的上下方(各一半,即 half-leading):
line-height = ascent + descent + leading
↑ 字体自带 ↑ 均分到上下
这解释了为什么给文字加 line-height 后,背景色区域会上下都"胖"出来一圈——那一圈就是 half-leading。
行框高度计算原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| /* 行框高度的构成 */
/* 单个行内盒子的高度 = line-height(含 half-leading) */
.ifc-line {
line-height: 24px; /* 定义行高 */
font-size: 16px; /* 影响 ascent */
}
/* 不同 vertical-align 会影响行框 */
.align-top {
vertical-align: top; /* 元素顶部对齐 */
}
.align-bottom {
vertical-align: bottom; /* 元素底部对齐 */
}
.align-middle {
vertical-align: middle; /* 元素中部对齐 */
}
.align-baseline {
vertical-align: baseline; /* 基线对齐,默认 */
}
|
行框高度计算示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| /* 行框高度计算示例 */
.line-box-demo {
line-height: 1.5; /* 行高1.5 */
}
.line-box-demo .normal {
font-size: 16px;
vertical-align: baseline;
}
.line-box-demo .tall {
font-size: 24px;
vertical-align: top; /* 会撑高行框 */
}
.line-box-demo .short {
font-size: 12px;
vertical-align: bottom; /* 也会影响行框 */
}
|
💡 一个必须知道的实战推论:行框高度取决于 line-height,不取决于 font-size。所以把某段文字的 font-size 调大后再调回来,行高可能不会变(因为 line-height 是独立的);反过来,想精确控制行高,改 line-height 比改 font-size 靠谱。
顺带一个经典现象:inline-block 元素之间那条几像素的"幽灵空隙",和这里的基线对齐有关——行内块默认按基线对齐,而基线下还要给"下伸部"(比如字母 g、y 的尾巴)留位置,所以父元素底部会多出一截。解决办法是给父元素设 line-height: 0 或 font-size: 0——这也是第 19 章里 font-size: 0 技巧的另一个理论来源。
20.3 flex/grid 格式化上下文
20.3.1 flex/grid 容器内部形成独立的格式化上下文,与 BFC 的作用有重叠但机制不同
准确的说法分两层:容器建立的是 flex / grid 格式化上下文(内部的排布规则完全换了一套);项目的内容则建立独立的格式化上下文,这一层和 BFC 效果一致。两者叠加的结果,就是下面这些"看起来像 BFC"的表现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| /* Flex/Grid 格式化上下文 */
/* Flex 容器 */
.flex-container {
display: flex; /* 内部形成 flex 格式化上下文 */
}
/* Grid 容器 */
.grid-container {
display: grid; /* 内部形成 grid 格式化上下文 */
}
/* Flex/Grid 的特性与 BFC 类似但不同 */
/* 注意:浮动对 flex 项目完全失效 */
.flex-container > .float-child {
float: left; /* ⚠️ 失效!flex 项目会被"块化",float 直接被忽略 */
margin-top: 20px; /* ⚠️ 项目之间也不会发生 margin 折叠 */
}
/* 包裹浮动元素 */
.flex-wrapper {
display: flex;
/* Flex 项目会被正确包裹 */
}
/* 阻止 margin 折叠 */
.grid-wrapper {
display: grid;
/* Grid 项目不会 margin 折叠 */
}
|
20.3.2 容器外的布局不会影响容器内,容器内也不参与外部的 margin 折叠
Flex/Grid 容器内部形成独立的布局区域,外部的布局变化不会影响内部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| /* Flex/Grid 容器的隔离性 */
/* 外部的 margin 折叠不会传入容器内 */
.outer-margin {
margin-top: 50px;
}
.flex-isolated {
display: flex;
/* 外部的 margin 不会影响内部布局 */
/* 内部的 margin 也不会与外部折叠 */
}
.flex-isolated .item {
margin: 10px;
/* 每个项目的 margin 是独立的,不会折叠 */
}
|
Flex/Grid 与 BFC 的对比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| /* 对比 Flex/Grid 和传统 BFC */
/* BFC 的触发方式 */
.bfc-overflow {
overflow: hidden;
}
.bfc-flow-root {
display: flow-root;
}
/* Flex/Grid 自动形成格式化上下文 */
.flex-context {
display: flex;
}
.grid-context {
display: grid;
}
/* 两者的共同点:包裹浮动、阻止 margin 折叠 */
/* 两者的区别: */
/* 1. BFC 是块级布局,Flex/Grid 是自己的布局系统 */
/* 2. Flex/Grid 的布局能力更强大 */
/* 3. Flex/Grid 不需要触发条件 */
|
本章小结
恭喜你完成了第二十章的学习!让我们来回顾一下这章的精华:
核心知识点
| 概念 | 说明 |
|---|
| BFC | Block Formatting Context,块级格式化上下文 |
| IFC | Inline Formatting Context,行内格式化上下文 |
| BFC 特性 | 包裹浮动、阻止 margin 折叠、阻挡外部环绕 |
| 触发 BFC | overflow 非 visible、display: flow-root、inline-block、table-cell、float 非 none、position: absolute/fixed、多列容器、contain: layout/paint/content;flex/grid 项目(不是容器) |
格式化上下文图解
graph TD
A["格式化上下文"] --> B["BFC(块级)"]
A --> C["IFC(行内)"]
A --> D["Flex/Grid"]
B --> B1["包裹浮动元素"]
B1 --> B2["阻止margin折叠"]
B2 --> B3["防止被外部浮动环绕"]
D --> D1["内部独立布局"]
D1 --> D2["不参与外部margin折叠"]
style A fill:#f39c12,stroke:#333,stroke-width:3px实战建议
- 清除浮动:使用
display: flow-root 或 overflow: hidden - 阻止 margin 折叠:让元素触发 BFC
- 复杂布局:使用 Flexbox 或 Grid,它们自带格式化上下文
- 理解原理:了解 BFC 的三个特性可以解释很多 CSS 布局问题
最后提醒一句:别把"触发 BFC"当成万金油。它解决的是"浮动外溢"“margin 折叠"这类由布局规则引起的问题;如果问题出在定位、层叠上下文或尺寸计算上,BFC 帮不上忙。诊断布局问题时,先用浏览器 DevTools 看清楚盒子的尺寸和位置,比凭直觉猜要快得多。
下章预告
下一章我们将学习浮动布局,看看 float 是如何工作的,以及它的常见问题和使用场景!