格式化上下文

一直都知道margin塌陷可以用overflow/float或增加border/padding解决,却一直不知道overflow/float为什么可以解决这个问题。今天彻查了一下,发现这是和BFC相关的。

格式化上下文

提到BFC,首先从格式化上下文开始。在常规流中的框,都属于一个格式化的上下文中。这个上下文可能是块格式上下文,可能是行内格式上下文,但不可能同时是块格式上下文和行内格式上下文。

块框参与块格式上下文,行内框参与行内格式上下文。(之前不懂这句话,不觉得块框只参与块格式上下文,行内框只参与行内格式上下文,直到理解了匿名框:可视化格式模型和各种框。我的理解就是,不符合规范的框都被符合规范的框圈了起来。)

BFC(块格式化上下文)

BFC(Block Formatting Context),直译为块级格式化范围。

是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。当涉及到可视化布局的时候,Block Formatting Context提供了一个环境,HTML元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。

简单说,BFC就是一个独立的容器,这个容器完全隔离,容器里box的布局和容器外互不相关,既不会被容器外的元素影响,也不会影响到容器外的元素。

如何触发BFC

元素满足以下任意一条件即可触发BFC:

  • 根元素
  • float的值不为none;
  • postion的值不为static或relative;
  • display的值为table-cell、table-caption、inline-block、flex或者inline-flex中的一个;(即不是块级框的块容器框,块容器框定义那边举例的是table-cell和inline-block)
  • overflow的值不为visible。

BFC的特征

  • 内部的Box会从包含块的顶部开始,在垂直方向一个接一个地放置;
  • Box垂直方向的距离由margin决定,属于同一个BFC的相邻两个Box的margin会发生重叠;
  • 每个元素的左外边界,与包含块的左边界相接触(对于从左往右的格式,否则相反),即使存在浮动也是如此,除非这个框建立了一个新的BFC;
  • 关于这一点,详细说一下。“即使存在浮动也是如此”,指的是有一个浮动元素和一个static元素,浮动元素和static元素都会与包含块的左边界相接触(哪怕两个元素重叠)。而后面“除非这个框建立了一个新的BFC”,指的则是关于多个float或inline-block元素会表现得不一一与左边界接触,而是并排显示,以及后文“关于BFC不会与float box重叠”等现象。这些元素都建立了新的BFC。
  • 相关问答,来自我在SegmentFault的提问关于BFC的特征,为什么说内部的box会在垂直方向一个接一个放置?,十分感谢Druidiurd大大的解答。

  • BFC的区域不会与float box重叠;

  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此;
  • 计算BFC高度时,浮动元素也参与计算。

关于BFC不会与float box重叠

  • 本身,如果元素不是一个BFC,那么周围的浮动元素可以覆盖(压住)这个元素。而如果这个元素是一个BFC,则浮动元素不会覆盖它,表现为它会移动(固定宽度)or收缩(宽度不定)避开浮动元素。
  • 但是,只有浮动元素出现在BFC元素之前,才会出现这个表现。如果浮动元素出现在BFC元素之后,只会按部就班地浮动在元素后面而已。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="wrapper">
<div class="left"></div>
<div class="box"></div>
</div>

.wrapper {
background: black;
padding: 20px;
}

.left {
float: left;
width: 100px;
height: 100px;
background: pink;
}

.box {
background: orange;
height: 200px;
}

粉色元素浮动,橙色元素被覆盖:

给橙色元素添加overflow: hidden,使橙色元素成为一个BFC。橙色元素不与float box重叠,避让开来:

BFC应用场景

1. 解决margin折叠的问题 - 利用BFC内部外部完全隔离的特性

至此,我也总算真正明白为什么overflow/float可以解决margin叠加的问题了。首先,CSS中外边距折叠的条件是:

  1. 必须是处于常规文档流中(没有float和绝对定位)的块级盒子,并且处于同一个BFC中;
  2. 没有inline盒子(经测试,如果是display: inline的盒子,没有填充物的0*0display: inline盒子仍无法阻止margin折叠,有填充物则可以阻止;而对于其他行内级元素,如display: inline-block/inline-table/inline-flex/inline-grid等,不需要内容即可以成功阻止margin折叠),没有空隙,没有padding和border将它们分隔开来;
  3. 都属于垂直方向上相邻的外边距。

正因如此,才衍生出了使用padding/border将父子(或祖子)两个盒子分隔开来,或给父元素(或祖先元素)增加overflow非visible属性/float非none属性,使得父元素(或者祖先元素)成为一个独立的BFC,与子元素不处于同一个BFC,破坏margin折叠的条件。

2. 自适应两栏/三栏布局 - 利用特性,BFC不会与float box重叠

  • 左右定宽中间自适应三列布局

  • 本质上,就是先浮动两个元素,然后触发第三个元素BFC,BFC则会自动收缩,绕开前面的两个浮动元素

  • 注意浮动元素需在BFC元素之前
  • 两列布局同理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="middle"></div>
</div>

.left {
float: left;
background: orange;
width: 100px;
height: 200px;
}

.right {
float: right;
background: pink;
width: 100px;
height: 200px;
}

.middle {
overflow: hidden;
height: 200px;
background: black;
}

3. 清除浮动 - 利用特性,计算BFC高度时,浮动元素也参与BFC计算

众所周知,可以给浮动元素的父元素设置overflow非visible来清除浮动,不再赘述。

IFC(行内格式化上下文)

行框(line box)

相对于块格式化上下文,在行内格式化上下文中,框一个接一个水平排列,起点是包含块的顶部,水平方向上的margin、border和padding在框之间得以保留(垂直方向上的margin不保留)。框在垂直方向上可以以不同的方式对齐:顶部或底部对齐,或根据其中文字的基线对齐。包含这些框的长方形区域会形成一行,叫做行框(line box)。

行框的宽度由它的包含块和包含块中的浮动元素决定,高度由行高计算规则决定。

行框的范围

通常,行框的左边接触到其包含块的左边,右边接触其包含块的右边。然而,浮动元素可能会处于包含块的边缘和行框边缘之间,即将行框的宽度挤小。

行框通常高度不同(如一行包含了一个高的图形,其他行只包含了文本)。

行内框(inline box)可能被分隔

如果几个行内框在水平方向上无法放入一个行框中,他们可以分配在两个或多个垂直堆叠的行框中。因此,一个段落就是行框在垂直方向上的堆叠。行框在堆叠时没有垂直方向上的分割且永不垂叠。

如果一个行内框超出包含它的行框的宽度,它会被分割为几个框,并且这些框会被分步到几个行框内。如果一个行内框不能被分割(如只包含单个字符,或者语言特殊的断字规则不允许在行内框里换行,或行内框有“nowrap”或“pre”值的“white-space”特性影响),此时,行内框会溢出行框。

如果一个行内框被分割,margin、padding和border在分割处没有视觉效果。

空的行内框会被忽略

行框是为了支持IFC中的行内级内容的需要而被创造的。不包含文本,不包含white-space为pre的字符,不包含具有非零margin、padding或者border的行内元素,且不包含其他在常规流中的内容(比如图片、inline blocks或inline tables),且没有以一个pre的新行结尾的行框,在以测量任何在它们里面的元素的位置为目的时,一定要背当做零高度行框来对待;在以其他目的时,一定要被当做不存在。

参考文献