什么是 BFC
什么是 BFC
BFC(Block Formatting Context,块级格式化上下文)是 CSS 渲染页面时的一个概念,指页面中一块独立的布局区域。
W3C 的解释是:BFC 决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。当涉及到可视化布局时,BFC 提供了一个环境,HTML 元素在其中按照块级布局的规则进行排列。
通俗地说:BFC 是一个独立的块级布局环境,内外浮动的影响以及父子 margin 合并会在这个边界处受到限制。这种“独立”并不意味着内外布局完全互不影响:内部内容仍可撑高容器,容器尺寸仍会影响外部布局。
需要注意:BFC 不是某个 CSS 属性,它是渲染过程中的一种布局区域。我们可以通过某些 CSS 属性值创建(开启)它,其中 display: flow-root 就是专门用于创建 BFC 的方式。
开启 BFC 能解决什么问题
一个元素创建了新的 BFC 后,会获得以下三个特性:
- 包含内部浮动:计算容器的自动高度时,会计入参与该 BFC 的浮动元素,避免仅有浮动子元素时的高度塌陷;
- 避让外部浮动:普通流中创建 BFC 的块级元素,其边框盒不会与同一外部 BFC 中浮动元素的 margin 盒重叠;空间足够时可排在浮动旁边,不足时可能移到下方;
- 阻止父子 margin 合并:该元素与其普通流子元素之间的垂直 margin 不再合并。
注意第 3 点的边界:开启 BFC 阻止的是容器与其普通流子元素之间的 margin 合并,并不保证容器自身与外部兄弟元素的 margin 不合并。例如,两个相邻的
display: flow-root块级元素,其上下 margin 仍可能合并;BFC 内部相邻普通流块级子元素之间的 margin 也仍可能合并。
哪些元素会创建 BFC
满足以下任一条件的元素会创建新的 BFC:
- 文档的根元素(
<html>,页面加载后就默认存在); - 浮动元素:
float不为none; - 绝对定位元素:
position: absolute或fixed(若其内部采用块布局,则创建 BFC);relative和sticky本身不会创建 BFC; display: inline-block;- 表格类:
display: table-cell(HTML 中默认是td、th);display: table-caption(默认是caption);display: table、table-row、table-row-group、table-header-group、table-footer-group、inline-table——它们会让内部生成匿名 table cell,间接创建 BFC;
- 块级盒子且
overflow不为visible且不为clip(即auto、scroll、hidden等); display: flow-root(最推荐,见下文);- flex 项:
display: flex / inline-flex容器的直接子元素(前提是该子元素自身不是 flex、grid 或 table 容器); - grid 项:
display: grid / inline-grid容器的直接子元素(前提同上); - 多列容器:
column-count或column-width不为auto; column-span: all的元素(即使没有包裹在多列容器中);contain: layout、content或strict的元素;container-type包含size或inline-size的元素会创建独立格式化上下文(内部采用块布局时为 BFC);仅设置scroll-state不会因此创建 BFC。
注意区分:flex/grid 容器本身创建的是 flex 格式化上下文(FFC)或 grid 格式化上下文(GFC),严格来说不是 BFC。它们的直接布局项之间不会发生 margin 合并;但布局项内部若仍采用普通块布局,其内部块级元素之间仍可能合并 margin。普通块布局的 flex/grid 项会为自己的内容创建 BFC;若布局项自身也是 flex/grid 容器,则创建相应的格式化上下文。
三大典型场景
1. 高度塌陷(包含内部浮动)
父元素没有高度、子元素又全部浮动时,父元素高度会塌陷为 0:
<div class="container">
<div class="float-item">我是浮动的子元素</div>
</div>
.container {
border: 2px solid #333;
/* 子元素浮动后,这里高度塌陷为 0,边框"包不住"子元素 */
}
.float-item {
float: left;
width: 200px;
height: 100px;
background-color: #74b9ff;
}
给父元素开启 BFC,即可让它"包住"浮动的子元素:
.container {
display: flow-root; /* 高度不再塌陷 */
}
2. 不被浮动元素覆盖(排除外部浮动)
右侧元素会被左侧浮动元素部分覆盖(文字环绕效果的由来):
<div class="float-box">左浮动</div>
<div class="normal-box">正常块级元素</div>
.float-box {
float: left;
width: 150px;
height: 100px;
background-color: #ffeaa7;
}
.normal-box {
/* 未开启 BFC:会与浮动元素重叠排布,文字环绕 */
/* 开启 BFC:空间足够时排在浮动右侧,不足时可能移到下方 */
overflow: hidden; /* 方式之一 */
}
开启 BFC 后,.normal-box 会避让左侧浮动;空间足够时排在它的右侧,不足时可能移到下方。这也是传统"两栏自适应布局"的经典原理。
3. 父子 margin 合并(margin 塌陷)
子元素的 margin-top 会"穿透"到父元素外面:
<div class="parent">
<div class="child">子元素</div>
</div>
.parent {
width: 300px;
background-color: #55efc4;
/* 未处理时:child 的 margin-top 会跑到 parent 外面 */
}
.child {
margin-top: 50px;
}
给父元素开启 BFC,父子的 margin 就不会再合并:
.parent {
display: flow-root;
}
最佳实践:display: flow-root
开启 BFC 的方式很多,但各有副作用:
| 方式 | 副作用 |
|---|---|
overflow: hidden | 会裁剪溢出内容、隐藏滚动 |
float: left | 元素自身脱离文档流 |
display: inline-block | 改变元素显示类型 |
position: absolute | 脱离文档流 |
display: flow-root | 专门创建 BFC,不额外引入裁剪、滚动或浮动/绝对定位 |
display: flow-root 的含义就是"创建一个块级盒子,且它是一个新的 BFC 根"。只需要创建 BFC、又想保留普通块布局时,应优先使用它,而不是借助 overflow: hidden 等附带行为。但它并非完全不改变布局:包含浮动、阻止父子 margin 合并等正是预期的变化。
总结
- BFC 是独立的块级布局环境,但不意味着内外布局完全互不影响;
- BFC 的三大作用:包含内部浮动、避让外部浮动、阻止父子 margin 合并;
- 创建 BFC 不会自动阻止容器与外部兄弟元素的 margin 合并,内部相邻普通流块级子元素的 margin 也仍可能合并;
- 创建 BFC 的方式很多(根元素、浮动、绝对定位、overflow、表格类、flex/grid 项等);只需要创建 BFC 时,首选
display: flow-root; - 不要为了开启 BFC 而开启,只在需要解决上述三类布局问题时使用。
参考资料
- MDN:块级格式化上下文
- MDN:外边距合并
- MDN:display
- W3C:外边距合并规则
- W3C:定位方式(relative、sticky、absolute、fixed)
- W3C:container-type 与查询容器
补充:现代布局都用 Flex/Grid,还需要学习 BFC 吗?
现代布局减少了“靠 BFC 修补浮动布局”的需求,但不意味着 BFC 已经过时。
以前与现在的区别
以前经常用 float 拼两栏、多栏布局,因此还需要额外处理父容器高度塌陷、浮动影响旁边内容等问题。“清除浮动”“开启 BFC”也就成了常见的布局技巧。
现在这类布局通常直接使用更适合的布局方式:
| 布局方式 | 常见用途 |
|---|---|
| Flex | 以一个主轴为中心排列元素,如导航栏、按钮组、两栏布局,以及内容对齐、空间分配 |
| Grid | 同时控制行和列,如卡片网格、整体页面分区 |
Flex/Grid 提供了专门的排列、对齐和空间分配机制,不必再靠浮动拼出这些布局,也就减少了为此处理浮动高度塌陷等问题的需求。W3C:Flex 布局概述
BFC 仍然有哪些用途?
需要理解,但不必把它当作日常布局的主工具。 它仍然适用于以下场景:
- 普通文档流中的父子 margin 合并:文章正文、内容容器等不一定使用 Flex/Grid,仍可能需要通过
display: flow-root阻止父子 margin 合并。 - 图片浮动、文字环绕:这是
float本来擅长的用途,Flex/Grid 的并排布局不能直接替代文字沿图片轮廓或边缘环绕的效果;包含浮动时仍可使用 BFC。 - 维护老项目:理解浮动、清除浮动和 BFC,有助于读懂、修复已有布局。
用了 Flex/Grid,就不会发生 margin 合并了吗?
不是。要区分直接布局项与它们内部的普通块级元素。
- Flex/Grid 的直接布局项之间不会发生 margin 合并。
- 布局项内部如果仍是普通块布局,其内部相邻块级元素之间仍可能发生 margin 合并。
- 所以,给外层设置
display: flex或display: grid,不意味着所有后代元素的 margin 都不会合并。
例如,一个 Flex 项内部包含两个普通段落,两个段落相邻的上下 margin 仍可能合并。W3C:Flex 容器与格式化上下文
实际开发怎么选择?
页面和组件布局优先用 Flex/Grid;需要保留普通块布局,又要包含浮动或阻止父子 margin 合并时,用
display: flow-root。
BFC 是理解 CSS 布局行为的基础概念,而不是与 Flex/Grid 竞争的另一套页面布局方案。