深入理解 CSS 中的外边距折叠及 BFC

5,024 阅读6分钟

作为一个前端工程师,在编写 CSS 时外边距折叠及 BFC 都是经常遇到的情况,今天就来做一下总结。

外边距折叠

什么是外边距折叠

在 CSS 中,两个或者多个普通流中相邻盒子的边距在垂直方向上会发生折叠的这种现象叫做外边距折叠。外边距折叠分为父子外边距折叠及兄弟外边距折叠。

示例1:父子外边距折叠

我们给 child 添加了 margin-top,却导致 body 整体下移。

示例2:兄弟外边距折叠

我们给 child1 设置了 margin-bottom:20px,给 child2设置了 margin-top: 50px,但最终的展现效果确实 50px(注意:如果 margin 设置负值的话是什么情况的?可以思考一下😯)

触发外边距折叠的条件

那么触发外边距折叠的条件是什么呢?W3C文档里已作出了说明,需要符合下面条件:

  • 都是普通流中的元素且属于同一个 BFC
  • 没有被 padding、border、clear 或非空内容隔开
  • 两个或两个以上垂直方向的「相邻元素」

注意这里的「相邻元素」可能是兄弟节点也可能是父子节点,比如:一个元素的 margin-top 和它的第一个普通流子元素的 margin-top;一个元素的 margin-bottom 和它下一个普通流兄弟的 margin-top;一个高度为 auto 元素的 margin-bottom 和它的最后一个子元素的 margin-bottom

如何避免外边距折叠

前面已经提到了触发外边距折叠的条件,如果要避免外边距折叠只需破坏掉触发的条件即可,比如创建一个 BFC。

对于创建 BFC 的详细方法,引用知乎用户的回答就是:

根据 BFC 的定义,两个元素只有在同一 BFC 内,才有可能发生垂直外边距的重叠,包括相邻元素、嵌套元素。要解决 margin 重叠问题,只要让它们不在同一个 BFC 内就行。对于相邻元素,只要给它们加上 BFC 的外壳,就能使它们的 margin 不重叠;对于嵌套元素,只要让父级元素触发 BFC,就能使父级 margin 和当前元素的 margin 不重叠。

当然也要规范写法,比如设置 margin 时,尽量使各个元素 margin 方向保持一致,这样也能提高 CSS 代码的可读性。

那么什么是 BFC 呢?以及如何创建一个 BFC 呢?接下来就来详细看一下。

BFC

什么是 BFC

BFC(Block Formatting Context)即块级格式化上下文,W3C 规范对此作了详细的描述,翻译过来大概如下:

  • 浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及 overflow 值不为visiable 的块级盒子,都会为他们的内容创建新的 BFC(块级格式上下文)。

  • 在 BFC 中,盒子从顶端开始垂直的一个接一个排列,两个盒子之间的垂直间距由他们的 margin 值决定,在同一个 BFC 中,两个相邻块级盒子的垂直外边距会产生折叠。

  • 在 BFC 中,每一个盒子的左外边缘会触碰到容器的左边缘,对于从右到左的格式来说,则触碰到右边缘。即使在浮动里也是这样的(尽管一个盒子的 line boxes 会因为浮动而收缩),除非这个盒子的内部创建了一个新的 BFC(由于浮动,在这种情况下盒子本身 将会变得更窄

Line Box: www.w3.org/TR/2002/WD-…

如何创建一个 BFC

通过上面的描述,创建一个 BFC 只需满足以下条件之一即可:

  • float 的值不为 none
  • overflow 的值不为 visible
  • position 的值不为 static 或者 relative
  • display 的值为 table-cell, table-caption, inline-block, flexinline-flex 其中之一

例如在本文开篇的第一个父子元素边距折叠的问题,我们只需在父元素上增加 overflow: hidden 触发 BFC 即可:

BFC 的使用

边距折叠的问题可以用 BFC 来解决,但触发 BFC 并不是解决边距折叠的充分条件,还要得到合理的运用,下面就是使用 BFC 来解决的一些问题。

使用 BFC 防止外边距折叠

对于兄弟元素,只要给它们加上 BFC 的外壳,对它们进行隔离,就能使它们的 margin 不折叠,例如:

<div class="parent">
  <div class="bfc">
    <div class="child child1">child1</div>
  </div>
	<!-- 本示例中只对其中一个加 bfc 外壳也可以,保证不在同一个 bfc 中-->
  <div class="bfc">
    <div class="child child2">child1</div>
  </div>
</div>

对于父子元素,只要让父级元素触发 BFC,就能使父级的 margin 和当前元素的 margin 不折叠。

使用 BFC 防止高度塌陷

例如我们在一个容器中,对其子元素进行了浮动处理,那么由于子元素脱离文档流,容器的高度会发生塌陷,这个时候就可以通过触发容器的 BFC 来解决。

可以对示例中的 parent 添加 overflow: hidden 来触发 BFC,从而使容器高度恢复:

当然,对于类似问题我们常常会通过 clearfix 清除浮动来解决,以保证普适性,不过 BFC 也不失为一种不错的解决办法。

使用 BFC 防止「文字环绕」

比如如下示例,我们希望 child2位于 child1 的右方,但是此时 child1child2 的一部分却重合了,这并不是我们想要的效果。

这是由于在 BFC 中,每一个盒子的左外边缘会触碰到容器的左边缘,对于从右到左的格式来说,则触碰到右边缘,即使在浮动里也是这样的(尽管一个盒子的 line boxes 会因为浮动而收缩)。而这里 p 元素的文本部分就进行了收缩,为浮动元素提供空间。

那么此时为了解决这个问题,只需触发 p 元素的 BFC 即可,比如为 p 元素添加一个 overflow: hidden

总结

  • 在 CSS 中,两个或者多个普通流中相邻盒子的边距在垂直方向上会发生折叠的这种现象叫做外边距折叠
  • 可以通过合理的创建 BFC 解决外边距折叠的问题
  • BFC(Block Formatting Context)即块级格式化上下文,具有 BFC 特性的元素是一个独立的容器,容器里面的元素不会在布局上影响到外面的元素
  • 可以通过如下方式创建 BFC:
    • float 的值不为 none
    • overflow 的值不为 visible
    • position 的值不为 static 或者 relative
    • display 的值为 table-cell, table-caption, inline-block, flexinline-flex 其中之一
  • BFC 可以用来解决外边距折叠问题、高度塌陷问题以及「文字环绕」问题等

本文代码示例地址:github.com/wangchi/blo…

相关参考