BFC 块级格式化上下文
BFC(Block Formatting Context) 是CSS 中独立的渲染隔离区域,区域内外的布局互不干扰、互不影响,所有的布局规则只在当前的BFC 内部生效。
BFC 是解决浮动高度塌陷、外边距(margin)折叠、实现两列、三列自适应布局的核心底层逻辑。
如何理解浮动高度塌陷?
- 比如说:你有一个父容器(比如一个卡片),里面有一个子元素。子元素设置了
float: left。结果你发现,父容器的高度变成了 0,就像它没有内容一样,导致后面的元素跑到了它下面,布局全乱了。 - 原因是:
- 脱离文档流: 当一个元素设置了浮动(
float),它就像“灵魂出窍”一样,脱离了正常的文档流。 - 父亲的错觉: 父容器在计算高度时,只会看“留在家里”(在文档流中)的孩子。因为浮动的孩子“飘”走了,父容器觉得家里没人,所以高度塌陷为 0。
- 脱离文档流: 当一个元素设置了浮动(
- 解决方法:给父容器添加
overflow: hidden或overflow: auto。- 原理是BFC 有一个特性——计算高度时会包含内部的浮动元素。一旦父元素成为 BFC,它就会把“飘走”的孩子强行拉回来算高度。
如何理解外边距折叠?
- 比如说你有两个垂直排列的
div。上面的div设置margin-bottom: 50px,下面的div设置margin-top: 20px。直觉上它们中间应该有 70px 的间距。 现实是它们中间只有 50px 的间距。或者,你在一个父容器里放了一个子元素,给子元素设置margin-top: 20px,结果父容器也跟着往下跑了 20px。 - 原因是:
- 合并规则: 在垂直方向上,相邻的块级元素(兄弟或父子),它们的 Margin 会相遇。浏览器为了“省事”(或者说是为了排版美观),规定:当两个 Margin 相遇时,取较大的那个值,而不是相加。这就是 Margin 折叠。
- 父子穿透: 如果父子元素之间没有“障碍物”(如边框、内边距),子元素的 Margin 会穿透父元素的边界,直接作用在父元素外部。
- 解决方法:给父元素设置
overflow: hidden或display: flow-root(现代推荐)。- 原理是BFC 的另一个特性——BFC 区域内部的 Margin 不会与外部发生折叠。一旦父元素变成 BFC,它就变成了一个独立的结界,子元素的 Margin 被锁在里面了。
一、如何生成一个独立的BFC 区域
块级元素默认不是BFC,必须满足特定CSS 属性才会触发BFC,html 是页面天然的顶级BFC 容器。
1.根元素html
整个页面的根布局容器,也就是html是默认最大的BFC,页面内所有的普通块级元素都在这个BFC 里,遵循从上到下垂直布局的默认规则。
2.设置了浮动的元素
float:left/right(float:none 不触发)
浮动会让元素脱离标准文档流,同时强制创建BFC,缺点是脱离文档流副作用大,易引发父元素高度塌陷。
3.非static 的定位元素
position: absolute / fixed / sticky / relative
只要不是默认的 static 定位,都会创建 BFC;其中 absolute/fixed 完全脱离文档流,relative 仅偏移不脱离。
4.行内块元素
display: inline-block
兼具行内元素不换行、块级元素可设宽高的特性,同时触发 BFC。
5.表格相关
display 属性display: table-cell / table-caption
表格类元素天然具备 BFC 特性,日常布局中极少使用。
6.溢出非可见 作为BFC 用的最多
overflow: hidden / auto / scroll(overflow:visible 不触发)
日常开发最常用、无副作用的 BFC 触发方式,仅开启独立渲染规则,不改变元素文档流状态。
7.弹性布局或网格布局
display: flex / inline-flex / grid / inline-grid
现代布局方案,父容器设为 flex/grid 后,直接成为 BFC,内部布局完全独立。
二、BFC 内部布局规则(区域内元素的排布逻辑)
BFC 内部有一套固定的布局准则,不受外部样式干扰:
1.块级盒子垂直排列
BFC 内的所有块级元素,默认从上到下依次垂直排布,和标准文档流的块级布局一致,是最基础的排布规则。
2.垂直 margin 边距折叠
同一个 BFC 内部**,相邻块级盒子的垂直外边距会合并,最终间距取两个 margin 的最大值,而非相加。
例:上方盒子margin-bottom:20px,下方盒子margin-top:30px,最终两者间距为 30px。
拓展:父子元素的垂直 margin、空块级元素的 margin,在同一 BFC 内也会折叠。
讲解:空块级元素的 Margin 折叠(自我吞噬)
-
现象:你写了一个空的
div(或者里面只有一个<br>),给它设置了margin-top: 20px和margin-bottom: 30px。你期望它占据 50px 的高度。结果它高度变成了 0,或者只占据了 30px(取最大值),仿佛这个元素消失了一样。 -
原理(如何理解):
- 首尾相接: 对于一个高度为 0、没有边框、没有内边距的空盒子,它的“顶部”和“底部”在同一个位置。
- 内外合并: 这个盒子的
margin-top(想推开上面的元素)和margin-bottom(想推开下面的元素)相遇了。因为它们都属于同一个 BFC 里的垂直间距,浏览器把它们合并了。
3.块级盒子触左边缘对齐
BFC 内的块级盒子,默认紧贴 BFC 容器左边缘排布,不会主动避让 BFC 内部的浮动元素。
这也是关键现象:浮动图片旁的文字 / 行内元素会环绕浮动,但块级盒子会直接钻到浮动元素下方,因为块级盒只遵循 BFC 边界规则,不识别浮动。
4.布局独立计算
BFC 是完全独立的渲染单元,内部元素的尺寸、位置计算只参考自身 BFC 规则,外部的浮动、margin 等样式不会影响内部布局。
三、BFC 与外部交互规则(核心实用价值,布局关键)
这是 BFC 支撑两列、三列布局、解决布局 bug 的核心规则,也是日常开发最常用的能力:
规则 1:BFC 会完整包裹内部浮动元素 → 解决「父元素高度塌陷」
高度塌陷原理
普通父元素(非 BFC)不会计算脱离文档流的浮动子元素高度,子元素浮动后,父元素无法被撑开,高度直接坍缩为 0,这就是高度塌陷。
BFC 的解决逻辑
BFC 的渲染规则会将内部浮动元素纳入高度计算范围,父元素触发 BFC 后,会主动包裹所有浮动子元素,高度被正常撑起,无需再用 clearfix 清除浮动。
规则 2:BFC 区域不会与外部浮动元素重叠 → 实现自适应两列 / 三列布局
核心原理
普通块级元素会无视外部浮动,直接钻到浮动元素下方;而 BFC 会主动识别外部浮动元素,自动收缩自身宽度,避让浮动区域,不发生重叠。这也是 BFC 支撑多列布局的核心逻辑。
四、利用BFC 实现两列式布局
方法一:float + overflow 可以实现两列式布局
使用html 语义化标签aside 侧边栏和main 主栏作为两列式布局的载体。
给aside 设置float:left,使aside 脱离当前文档流称为一个浮动元素,同时向父容器左边靠。
main 是普通块级元素(默认占满整个宽度),它会无视浮动元素,自动移动到父容器的顶部占满整个宽度并被aside 覆盖重叠(aside 已经是浮动元素,脱离文档流了)左边的200px。
给 main 设置 overflow: hidden 使其形成 BFC。
此时main 是BFC,根据BFC 的特性,BFC 不会与浮动元素重叠,因此 main 会自动紧贴 aside 右侧排列,实现两栏布局。
- 当
main触发 BFC 后,它计算宽度的公式变成了:- main 的宽度=父容器宽度−aside 的宽度main 的宽度=父容器宽度−aside 的宽度
代码展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.layout {
color: #fff;
padding: 0 100px;
min-width: 500px;
}
h4 {
position: relative;
color: #333;
margin: 20px 0 10px;
}
h4::before {
content: '#';
position: absolute;
left: -20px;
top: -1px;
font-size: 18px;
color: #38ab89;
}
section {
margin-bottom: 50px;
}
aside {
width: 200px;
height: 50px;
}
.orange {
background-color: orange;
}
.green {
background-color: green;
}
.float {
/* 关键代码 */
float: left;
}
.hidden {
/* 关键代码 */
overflow: hidden;
}
</style>
</head>
<body>
<div class="layout">
<h4>方法一:float + overflow</h4>
<section>
<aside class="orange float">aside</aside>
<main class="green hidden">main</main>
</section>
</div>
</body>
</html>
方法二:float + margin-left 可以实现两列式布局
给 aside 设置 float: left,使其脱离文档流并向父容器左侧靠拢。
后面的 main 是普通块级元素(默认占满整个宽度),它会无视浮动元素,自动移动到父容器的顶部并尝试占满整个宽度,导致其左侧部分被 aside 覆盖重叠。
给 main 设置 margin-left: 200px(值等于 aside 的宽度)。
利用外边距产生的占位空间,把 main 的内容区域整体“挤”到 aside 的右侧,从而避开了浮动区域。
main 不再与浮动元素发生重叠,因此会自动紧贴 aside 右侧排列,实现两栏布局。
这个方法并没有用到BFC 特性,但也是实现两列式布局的好方法。
代码展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.layout {
color: #fff;
padding: 0 100px;
min-width: 500px;
}
h4 {
position: relative;
color: #333;
margin: 20px 0 10px;
}
h4::before {
content: '#';
position: absolute;
left: -20px;
top: -1px;
font-size: 18px;
color: #38ab89;
}
section {
margin-bottom: 50px;
}
aside {
width: 200px;
height: 50px;
}
.orange {
background-color: orange;
}
.green {
background-color: green;
}
/* 浮动:左边固定宽度 */
.float {
float: left;
}
/* 关键:margin 代替 overflow */
.mian {
margin-left: 200px;
/* 👈 左边宽度是200px,这里就给200px外边距 */
}
</style>
</head>
<body>
<div class="layout">
<h4>方法二:float + margin</h4>
<section>
<aside class="orange float">aside</aside>
<main class="green main">main</main>
</section>
</div>
</body>
</html>
五、三列式布局如何实现
圣杯布局方式
给父容器设置左右 padding,预留出两侧的空白区域;
中间栏 main 不设置宽度,作为普通块级元素自动占满父容器的内容区域;
左右栏使用 position: relative 进行相对定位,配合负的 left / right 偏移值,将侧边栏“拉”进父容器的 padding 留白区域;
中间栏保持在正常文档流中排列,不受浮动影响;左右栏则通过定位偏移实现占位;
最终实现两侧固定宽度、中间自适应的经典三栏布局。
代码展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.layout {
color: #fff;
padding: 0 100px;
min-width: 500px;
}
.w150 {
width: 150px;
height: 250px;
}
main {
height: 300px;
}
.orange {
background-color: orange;
}
.green {
background-color: green;
}
.grail {
padding: 0 150px;
/* 父容器留白 */
overflow: hidden; /* 触发 BFC,包含浮动,避免父元素高度塌陷 */
}
.grail main {
width: 100%;
float: left;
}
.grail .left {
position: relative;
margin-left: -100%;
float: left;
left: -150px;
}
.grail .right {
float: left;
position: relative;
margin-left: -150px;
right: -150px;
}
</style>
</head>
<body>
<div class="layout">
<h4>方法一:圣杯布局</h4>
<section class="grail clearfix">
<main class="green">main</main>
<aside class="w150 left orange">aside</aside>
<aside class="w150 right orange">aside</aside>
<!-- 父容器所有元素都浮动,会造成父容器高度塌陷,导致内容被遮挡
解决方法:添加伪元素清除浮动
.clearfix::after {
content: "";
display: table;
clear: both;
} -->
</section>
</div>
</body>
</html>