5.1.2 块格式化上下文
块级框参与的格式化上下文,称作块格式化上下文(Block Formatting Contexts,简称BFC),它规定了内部的块级框如何排列。
块格式化上下文看似抽象,其实比较简单,它实际上就是页面上的一个块级元素,只是在布局上,该元素内部的元素和外部的元素相互独立,互不影响。
通俗的讲,就是在创建了块格式化上下文的元素中,其子元素都会按照块格式化上下文的规则排列自己。以下元素都会自动为其内容创建一个块格式化上下文:
- 根元素
- 浮动的元素(float: left | right)
- 固定定位的元素(position: absolute | fixed)
- overflow 属性值为 hidden | auto | scroll 的元素
- display 属性值为 table | table-caption | table-cell | flex的元素
- 表格的单元格(td、th)
- 表格的标题(display: table-captions,caption)
块格式化上下文具有以下特点:
1)在块格式化上下文中,如果只存在块级框,则所有块级框从包含块的顶部开始,一个接一个地垂直排列,每个框被渲染为完整的一行。
假设在 body 下,有一个 class = "wrapper" 的 div 子元素,该子元素又包含三个 div 子元素:
<html>
<head><title>BFC</title>
<body>
<div class = "wrapper">
<div style = "height: 40px;"></div>
<div style = "width: 100px; height: 40px;"></div>
<div style = "width: 100px; height: 20px;"></div>
</div>
</body>
</html>
上述代码中,html 元素是根元素,body 元素是根元素唯一的子元素,.wrapper 又是 body 的子元素。
为了便于观察,为根元素 html 设置了外边距、内边距和红色边框,为 body 元素设置了内边距和黑色边框,为 .wrapper 子元素设置宽度、内边距和蓝色边框,还为其子元素设置上下外边距和灰色边框:
html {
margin: 10px;
padding: 10px;
border: 1px solid red;
}
body {
padding: 20px;
border: 1px solid black;
}
.wrapper {
width: 300px;
padding: 10px;
border: 1px solid blue;
}
.wrapper div {
margin: 10px 0;
border: 1px dashed gray;
}
由创建BFC的条件可知,根元素默认会创建一个BFC,而 body 和 .wrapper 都不符合创建BFC的条件。因此,body、.wrapper 及 .wrapper 的三个子元素,都将参与根元素创建的块格式化上下文。
前面曾经介绍过,所有元素都是在其包含块中布局。由此可知,body 在初始包含块中布局,.wrapper 在 body 创建的包含块中布局,.wrapper 的三个子元素在 .wrapper 创建的包含块中布局,并从包含块的顶端开始,一个接一个地垂直排列。运行结果如图 5‑8 所示:
从上图可以看出,虽然 .wrapper 的第二个和第三个子元素的宽度都是 100px,它们也独占一行。由此可知,在BFC中,每个块级框始终独占一行,即便一行的空间足以容纳多个框也是如此。
2)在同一个块格式化上下文中,相邻两个框之间的垂直距离,由它们的垂直外边距计算得到。当两个垂直外边距相遇时,它们之间的垂直距离,不是第一个框的 margin-bottom 与第二个框的 margin-top 之和,而两者中的较大者。
在CSS中,把这个现象称作外边距合并,即 margin 的合并,意思是说较小的 margin,被合并到了较大的 margin 之中。如图 5‑9 所示:
外边距合并时,当两个相邻的外边距都是正数时,合并结果是两者中的较大者;都是负数时,合并结果是两者绝对值较大者;一正一负时,合并结果是两者之和。
只要是两个垂直外边距相遇,不管这两个元素之间是父子关系还是兄弟关系,都会发生外边距合并。当一个元素包含在另一个元素中时(假如没有内边距或边框把外边距隔开),它们相邻的外边距也会发生合并。如图 5‑10 所示:
一个元素自身的垂直外边距也可以发生合并,假设有一个空元素,它有上下外边距,但没有边框和内边距。这种情况下,上外边距与下外边距就碰到了一起,它们就会发生合并。如图 5‑11 所示:
外边距合并看上去似乎有点奇怪,但在页面布局中,它却有存在的现实意义。假设有一组块级框,它们都设置了相同上下外边距。如果没有外边距合并,第一个框和最后一个框之外的所有框之间的垂直外边距,都将是相邻上外边距和下外边距之和,这意味着这些框之间的空间将会加倍。如果发生外边距合并,框之间的上外边距和下外边距就合并,所有框之间的垂直距离就会保持一致。如图 5‑12 所示:
只有同一个块格式化上下文中,块级元素的垂直外边距会发生合并,而浮动元素或绝对定位元素属于不同的BFC,它们之间的外边距不会合并。
3)在块格式化上下文中,每个块级框的左外边界,都紧贴包含块的左内边界(对于从右往左的格式化,则为框的右外侧紧贴包含块右侧),即便存在浮动也是如此,除非这个块级框创建了一个新的BFC。
假设在一个容器中,有一个 div 子元素和一个段落子元素:
<div class = "wrapper">
<div></div>
<p>在BFC中,每个框的左外边界紧贴…除非框创建了一个新的BFC。</p>
</div>
为了方便查看容器中内容的对齐情况,为容器设置宽度、内边距和边框,并让div 向左浮动,为两个子元素设置不同的背景颜色:
.wrapper {
width: 300px;
padding: 10px;
border: 1px solid #ccc;
}
.wrapper div {
float: left;
width: 60px;
height: 60px;
background: #f90;
}
p {
color: #fff;
background: #2595e5;
}
上述代码的运行结果如图 5‑13 所示:
从上图可以看出,浮动框和非浮动框的左外边界,都紧贴包含块的左内边界。其实,这也容易理解,因为浮动框已经脱离文档流,它的块级兄弟元素,会无视浮动框的存在,尽量占满一整行,导致被浮动框覆盖。
正如上图 所示,这里的 p 元素仍然紧贴包含块的左内边界,导致段落被浮动框覆盖,而 p 元素中的文本所形成的行框却向右移动,并水平变窄来给浮动元素腾出空间。随着文本的增加,后面的文本又会紧贴包含块的左内边界,因为它不再受浮动框的影响,便无需再向右移动了。
有时候,可以利用这个特性来创建文本环绕的效果,但有时候,这种情况并非所愿。如果设置段落的左外边距,使 p 元素为浮动元素腾出空间,就不会被浮动元素覆盖。
不过,如果让段落创建一个新的BFC,也不会被浮动框覆盖(但是,p 元素所生成的框,会受浮动框影响而变窄)。如:
p {
color: #fff;
overflow: hidden;
background: #2595e5;
}
运行结果如图 5‑14 所示:
这里使用 overflow 属性来创建BFC,使 p 元素向右移动并水平变窄,来给浮动元素腾出空间,以便不被浮动元素覆盖。当然,在实际应用中,可以根据实际情况,选择任意合适的属性来创建BFC。
4)计算块格式化上下文的高度时,浮动元素也参与计算,也就是说,创建了BFC的元素,会根据子元素情况自动适应高度,可以让一个元素包含浮动元素。在 5.3.2 节将详细介绍如何利用这个规则来间接实现清理浮动的效果,这里不再赘述。
前面已经介绍,只有块级框会参与块格式化上下文,那么,当一个块格式化上下文中既有块级框,又有行内级框时,该如何布局呢?考虑以下HTML代码片段:
<body>
<div class = "wrapper">
some text
<div>div</div>
<span>span1</span>
<span>span2</span>
</div>
</body>
为了方便查看每一行的效果,为 div 和 span 设置边框,为 div 设置上下外边距:
.wrapper div,
.wrapper span {
border: 1px dashed #ccc;
}
.wrapper div {
margin: 10px 0
}
运行结果如图 5‑15 所示:
从上图可以看出,块级框中的文本“some text”生成一个匿名块级框直接参与BFC,div 元素生成一个块级框直接参与BFC,而 span1 和 span2 元素生成的行框(相当于一个匿名块级框)再参与BFC。
综上所述,普通流中页面布局基本的规则为:首先,为根元素创建一个BFC,所有子元素参与到该BFC中。然后,任何子元素都可以根据需要创建新的BFC,它的所有子元素再参与到该BFC中。这样,通过BFC的层层嵌套,就可以构建出千变万化的布局效果。