先记住这个答案
BFC 是块级格式化上下文,可以理解成一块独立的布局区域:里面的浮动、外边距只在里面生效,不会影响外面。常见的触发方式有根元素 html、float 不为 none、position 为 absolute 或 fixed、display 为 inline-block、flow-root、table-cell,overflow 不为 visible 和 clip,以及 flex 和 grid 的子项。实际用途有三个:父元素包住浮动子元素,高度不塌陷;BFC 盒子不和旁边的浮动元素重叠,可以做两栏自适应;阻止父子之间的外边距合并。现在首选 display: flow-root,它就是为这件事设计的,没有别的副作用。
- 首选
display: flow-root,没有副作用 - 能包住内部浮动,父元素高度不再塌陷
- 不会和外部浮动重叠,可做两栏自适应
- BFC 内外的外边距不合并,内部兄弟照样合并
最常见的两个用途:包住浮动和避开浮动
浮动元素会脱离普通流,父元素算高度时默认不算它,这就是高度塌陷。旁边的普通块也会当它不存在,伸到它下面去,只有文字会绕开。BFC 把这两个问题都解决了。
<style>
.box { display: flow-root; border: 1px solid #333; } /* 建立 BFC,包住浮动 */
.avatar { float: left; width: 80px; height: 80px; }
.content { display: flow-root; } /* 建立 BFC,不再伸到 .avatar 下面 */
</style>
<div class="box">
<img class="avatar" src="avatar.png" alt="">
<div class="content">一段很长的介绍文字……</div>
</div>
<!-- .box 高度至少 80px,边框包住头像 -->
<!-- .content 从头像右侧开始,宽度自动占满剩余空间,文字不会绕到头像下方 -->.box 如果不设 display: flow-root,里面只有浮动元素和一段短文字时,边框高度只有文字那么高,头像下半截露在外面。建立 BFC 后,浏览器计算 .box 高度时会把浮动的 .avatar 算进去。
.content 本来是普通块,它的盒子会从 .box 最左边开始,只是文字绕开头像。建立 BFC 后,整个盒子都缩到头像右边,文字再多也不会绕回头像下方。这就是经典的「左边固定宽度、右边自适应」两栏布局,右栏不用写宽度,也不用写 margin-left。
第三个用途:隔断外边距合并
父元素没有 border、padding 时,第一个子元素的 margin-top 会穿透出去,和父元素的 margin-top 合并,表现成父元素整体往下移,子元素却贴着父元素顶部。
<style>
.card { background: #eee; }
.title { margin-top: 30px; }
.card.fixed { display: flow-root; } /* 建立 BFC 后,30px 留在 .card 内部 */
</style>
<div class="card"><h3 class="title">标题</h3></div>
<!-- 灰色背景的上方出现 30px 空白,标题贴着灰色顶边 -->
<div class="card fixed"><h3 class="title">标题</h3></div>
<!-- 灰色背景从上一个元素下面直接开始,标题和灰色顶边之间有 30px -->建立 BFC 的元素,它的外边距不会和自己的子元素合并,所以 .title 的 30px 被关在了 .card 里面。给 .card 加 padding-top: 1px 或者 border-top 也能解决,但会改变尺寸,BFC 的做法不影响盒子大小。
- 兄弟元素的合并要单独处理
- 同一个 BFC 里相邻的两个兄弟,上面的
margin-bottom和下面的margin-top照样合并,取较大的那个。想让它们不合并,要把其中一个包进一个新的 BFC 容器里,让两个外边距分属不同的 BFC。
各种触发方式的副作用
触发方式很多,但除了 flow-root,每一种都带着别的效果,选错了会引出新问题。
overflow: hidden 是老项目里最常见的,它会裁掉超出容器的内容,比如卡片阴影、绝对定位的下拉菜单、tooltip。overflow: auto 内容一多就出滚动条。float 会让元素本身也变成浮动,还得再处理它的父元素。position: absolute 直接脱离文档流。display: inline-block 会让元素变成行内级,宽度收缩到内容大小,还会带出空白间隙。display: flow-root 只建立 BFC,别的什么都不改,现代浏览器都支持,新代码没有理由不用它。
另外要注意,display: flex 和 display: grid 的容器建立的是 flex 和 grid 格式化上下文,不是 BFC。它们同样能包住内部元素,也不和外部浮动重叠,但内部的浮动会失效,外边距也不合并,和 BFC 的规则是两套。
容易答错的地方
- 以为
overflow: clip也能触发 BFC overflow: clip只裁剪,不建立 BFC,这是它和overflow: hidden的主要区别之一。它也不会让元素变成滚动容器,想要裁剪又不想要 BFC 时正好用它。- 以为 BFC 能消灭所有外边距合并
- BFC 只隔断「BFC 内部」和「BFC 外部」之间的合并。BFC 内部两个相邻兄弟之间的合并不受影响,前面讲过要单独处理。
- 以为 BFC 会裁剪溢出内容
- BFC 只影响布局计算,不负责裁剪。容器写了固定
height,内部浮动更高时,浮动元素照样溢出显示。会裁剪的是overflow: hidden本身,不是 BFC。
面试官还会怎么问?
BFC 和层叠上下文有什么关系?
两个不相干的概念。BFC 管平面上的布局,也就是浮动怎么排、外边距怎么算;层叠上下文管 z 轴上谁盖住谁。opacity: 0.5、transform 会建立层叠上下文,但不建立 BFC;overflow: hidden 建立 BFC,但本身不建立层叠上下文。
清除浮动除了 BFC 还有什么办法?
传统做法是 clearfix:给父元素加 ::after 伪元素,设 content: ''、display: block、clear: both。伪元素排在浮动元素下面,把父元素撑开。它和 flow-root 的区别是只解决高度塌陷,不阻止外边距合并,也不影响旁边的浮动。
为什么现在很少讨论 BFC 了?
因为布局主力已经从浮动换成了 flex 和 grid,高度塌陷、两栏布局这些问题在 flex 和 grid 里本来就不存在。但文字环绕图片这类场景还在用浮动,维护老项目也常遇到外边距穿透,BFC 仍然是排查这类问题的基础。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。