Appearance
BFC 块级格式化上下文
问题
BFC 是什么?它能解决哪些布局问题?
结论
理解路径
- 页面布局不是只计算单个元素的位置,还要处理元素之间的相互影响,例如块盒排列、浮动避让、内部浮动高度计算和 margin 折叠。
- CSS 2.1 / CSS 2.2 的视觉格式化模型把这些相互影响抽象为 formatting context。
- 常见 formatting context 包括 BFC(Block Formatting Context,块级格式化上下文)和 IFC(Inline Formatting Context,行内格式化上下文)。
- BFC 负责一类块级布局规则,核心影响块级盒排列、浮动参与高度计算、外部浮动避让和垂直 margin 折叠。
- 因此 BFC 常被用来解释清除浮动、两栏布局和 margin 折叠隔离。
定义
CSS 布局不是只有一套规则。浏览器会根据盒子的类型、display、定位、浮动等条件,把元素放进不同的 formatting context 中;每种 formatting context 都有自己的布局规则,用来决定内部盒子如何排列,以及它们如何和浮动、margin 等布局关系相互影响。
BFC 是 Block Formatting Context,块级格式化上下文,是 formatting context 的一种。它可以理解成一块按块布局规则计算的独立布局区域,不是某个 CSS 属性。
BFC 主要影响三类布局关系:
创建 BFC 的情况
常见会创建 BFC 的情况包括:
- 根元素
<html>。 - 浮动元素:
float不是none。 - 绝对定位或固定定位元素:
position: absolute/fixed。 display: inline-block。- 表格相关盒:
table-cell、table-caption,以及由display: table等隐式创建的匿名表格盒。 - 块级盒设置
overflow,且值不是visible或clip。 display: flow-root。contain: layout、contain: content、contain: paint。- flex item:
display: flex/inline-flex容器的直接子元素,且该子元素本身不是 flex、grid 或 table 容器。 - grid item:
display: grid/inline-grid容器的直接子元素,且该子元素本身不是 flex、grid 或 table 容器。 - 多列容器:
column-count或column-width不是auto,包括column-count: 1。 column-span: all。
display: flex / display: grid 建立的是 flex / grid formatting context,不应直接说成“这个元素创建 BFC”。但 flex item、grid item 在满足条件时会创建 BFC。
布局规则
- 在 BFC 中,普通流里的块级盒会沿 一个接一个排列。
- 同一个 BFC 的普通流中,相邻块级盒之间的垂直距离由 margin 决定,。
- 普通流块盒的 margin box 外边缘会与包含块的 border box 外边缘相接触;即使存在浮动,块盒本身的边界仍按包含块计算,通常是内部行盒被浮动挤开。
- 建立 BFC 的元素在计算自动高度时,会包含其内部浮动元素。
- 正常流中建立 BFC 的元素,其 border box 不会与同一上下文中的浮动元素 margin box 重叠。
- BFC 可以理解成一个 。
应用场景
包含内部浮动
父容器没有建立 BFC 时,内部浮动不会撑开父容器的自动高度;建立 BFC 后,父容器计算自动高度时会包含内部浮动。
现代写法优先使用:
css
.container {
display: flow-root;
}overflow: hidden 也能创建 BFC,但会改变溢出行为,可能裁剪阴影、下拉菜单或绝对定位溢出内容。
避开外部浮动
下面的 .content 通过 display: flow-root 建立 BFC,因而会整体避开左侧浮动的 .sidebar。早期的自适应两栏布局常利用这个特性:
css
.sidebar {
float: left;
width: 160px;
}
.content {
display: flow-root;
}隔离 margin 折叠
相邻块级盒的垂直 margin 可能折叠。要隔离折叠,不是随便把某个 p 变成 BFC,而是把其中一个元素放进新的 BFC 容器。
html
<p class="first">第一个段落</p>
<div class="bfc-wrapper">
<p class="second">第二个段落</p>
</div>css
.first,
.second {
margin-block: 24px;
}
.bfc-wrapper {
display: flow-root;
}垂直 margin 折叠如何计算
普通块流中相邻块盒的垂直 margin 可能折叠,折叠后的距离不是简单相加。
css
p {
margin-block-start: 10px;
margin-block-end: 15px;
}html
<p>AAA</p>
<p></p>
<p></p>
<p></p>
<p>BBB</p>这些空段落的上下 margin 会连续折叠在一起。全部都是正 margin 时,折叠结果取最大值,所以 AAA 和 BBB 之间的间距通常是 15px,不是多个段落 margin 的总和。
折叠计算要注意:
- 全部为正值时,取最大正值。
- 全部为负值时,取绝对值最大的负值。
- 正负混合时,最大正值和最小负值相加。
- flex/grid 项目之间不会按普通块流规则发生这种垂直 margin 折叠。
什么是 margin 重叠问题?如何解决?
两个块级元素在垂直方向上相邻时,它们的 margin 会合并(折叠)为一个,取较大值,而不是相加。这只发生在垂直方向,水平方向不折叠;浮动元素和绝对定位元素不会折叠。
折叠计算规则:
- 两者都为正数:取较大值;
- 一正一负:正值减去负值的绝对值;
- 两者都为负数:取绝对值较大的那个(结果仍为负)。
折叠发生的两类情况及解决方法:
(1)兄弟元素之间折叠
css
/* 两个相邻块,margin 会折叠 */
.a { margin-bottom: 20px; }
.b { margin-top: 30px; }
/* 两者之间实际间距为 30px,不是 50px */解决:把其中一个变成 BFC 的成员(如 display: inline-block、float),或把其中一个包裹在新建 BFC 的容器里。
(2)父子元素之间折叠(父元素没有 border/padding 隔离时)
css
.parent { /* 没有 border 或 padding */ }
.child { margin-top: 30px; }
/* 子元素的 margin-top 会"穿透"父元素,与父元素外部的 margin 折叠 */解决:
- 父元素加
overflow: hidden(触发 BFC); - 父元素加透明边框
border: 1px solid transparent; - 父元素加
padding-top; - 子元素改用
padding-top或改变显示方式。
常见误区
- BFC 是布局上下文,不是 CSS 属性。
display: flex/grid不等于创建 BFC;它们建立的是 flex / grid formatting context。overflow: hidden可以创建 BFC,但不应只为了 BFC 默认使用它。- BFC 只能隔离部分布局影响,不会隔离继承、定位、绘制等所有 CSS 影响。
Demo
html
<section class="card">
<img class="thumb" src="/images/example.png" alt="">
<p>内部图片设置 float 后,父容器仍需要包住它。</p>
</section>css
.card {
display: flow-root;
border: 1px solid #94a3b8;
}
.thumb {
float: left;
width: 120px;
margin-right: 16px;
}这个例子验证的是:建立 BFC 的容器在计算自动高度时会包含内部浮动。