在一个Web页面的CSS渲染中, 块级格式化上下文 (Block Fromatting Context)是按照块级盒子布局的。W3C对BFC的定义如下:
浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不为“visiable”的块级盒子,都会为他们的内容创建新的BFC(块级格式上下文)。
为了便于理解,我们换一种方式来重新定义BFC。一个HTML元素要创建BFC,则满足下列的任意一个或多个条件即可:
1、 float
的值不是 none
。
2、 position
的值不是 static
或者 relative
。
3、 display
的值是 inline-block
、 table-cell
、 flex
、 table-caption
或者 inline-flex
4、 overflow
的值不是 visible
BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
要显示的创建一个BFC是非常简单的,只要满足上述4个CSS条件之一就行。例如:
<div class="container">
你的内容
</div>
在类 container
中添加类似 overflow: scroll
, overflow: hidden
, display: flex
, float: left
,或 display: table
的规则来显示创建BFC。虽然添加上述的任意一条都能创建BFC,但会有一些副作用:
1、 display: table
可能引发响应性问题
2、 overflow: scroll
可能产生多余的滚动条
3、 float: left
将把元素移至左侧,并被其他元素环绕
4、 overflow: hidden
将裁切溢出元素
因而无论什么时候需要创建BFC,都要基于自身的需要来考虑。对于本文,将采用 overflow: hidden
方式:
.container {
overflow: hidden;
}
如前文所说,在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。W3C给出得规范是:
在BFC中,每一个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说,则触碰到右边缘)。浮动也是如此(尽管盒子里的行盒子 Line Box 可能由于浮动而变窄),除非盒子创建了一个新的BFC(在这种情况下盒子本身可能由于浮动而变窄)。
常规流布局时,盒子都是垂直排列,两者之间的间距由各自的外边距所决定,但不是二者外边距之和。
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
</div>
对应的CSS:
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}
p {
background-color: lightgreen;
margin: 10px 0;
}
渲染结果如图:
在上图中,一个红盒子( div
)包含着两个兄弟元素(p),一个BFC已经创建了出来。
理论上,两个 p
元素之间的外边距应当是二者外边距之和(20px)但实际上却是10px,这是外边距折叠(Collapsing Margins)的结果。
在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。折叠的结果按照如下规则计算:
1、两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
2、两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
3、两个外边距一正一负时,折叠结果是两者的相加的和。
产生折叠的必备条件:margin必须是邻接的!(对于不产生折叠的情况,见参考文章的链接)
BFC可能造成外边距折叠,也可以利用它来避免这种情况。BFC产生外边距折叠要满足一个条件: 两个相邻元素要处于同一个BFC中。 所以,若两个相邻元素在不同的BFC中,就能避免外边距折叠。
改进前面的例子:
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
<p>Sibling 3</p>
</div>
对应的CSS:
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}
p {
background-color: lightgreen;
margin: 10px 0;
}
结果和上面一样,由于外边距折叠,三个相邻P元素之间的垂直距离是10px,这是因为三个 p 标签都从属于同一个BFC。
修改第三个P元素,使之创建一个新的BFC:
<div class="container">
<p>Sibling 1</p>
<p>Sibling 2</p>
<div class="newBFC">
<p>Sibling 3</p>
</div>
</div>
对应的CSS:
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}
p {
margin: 10px 0;
background-color: lightgreen;
}
.newBFC {
overflow: hidden; /* creates new block formatting context */
}
现在的结果如图:
因为第二个和第三个P元素现在分属于不同的BFC,它们之间就不会发生外边距折叠了。
浮动元素是会脱离文档流的(绝对定位元素会脱离文档流)。如果一个没有高度或者 height
是 auto
的容器的子元素是浮动元素,则该容器的高度是不会被撑开的。我们通常会利用伪元素( :after
或者 :before
)来解决这个问题。BFC能包含浮动,也能解决容器高度不会被撑开的问题。
看个例子:
<div class="container">
<div>Sibling</div>
<div>Sibling</div>
</div>
CSS:
.container {
background-color: green;
}
.container div {
float: left;
background-color: lightgreen;
margin: 10px;
}
在上面这个例子中,容器没有任何高度,并且它包不住浮动子元素,容器的高度并不会被撑开。为解决这个问题,可以在容器中创建一个BFC:
.container {
overflow: hidden; /* creates block formatting context */
background-color: green;
}
.container div {
float: left;
background-color: lightgreen;
margin: 10px;
}
现在容器可以包住浮动子元素,并且其高度会扩展至包住其子元素,在这个新的BFC中浮动元素又回归到页面的常规流之中了。
如上图所示,对于浮动元素,可能会造成文字环绕的情况(Figure1),但这并不是我们想要的布局(Figure2才是想要的)。要解决这个问题,我们可以用外边距,但也可以用BFC。
First let us understand why the text wraps. For this we have to understand how the box model works when an element is floated. This is the part I left earlier while discussing the alignment in a block formatting context. Let us understand what is happening in Figure 1 in the diagram below:
假设HTML是:
<div class="container">
<div class="floated">
Floated div
</div>
<p>
Quae hic ut ab perferendis sit quod architecto,
dolor debitis quam rem provident aspernatur tempora
expedita.
</p>
</div>
上图整个黑色区域表示 p 元素。p 元素没有移位但它叠在了浮动元素之下,而p元素的文本(行盒子)却移位了,行盒子水平变窄来给浮动元素腾出了空间。随着文本的增加,最后文本将环绕在浮动元素之下,因为那时候行盒子不再需要移位,也就成了图Figure1的样子。
再回顾一下W3C的描述:
在BFC上下文中,每个盒子的左外侧紧贴包含块的左侧(从右到左的格式里,则为盒子右外侧紧贴包含块右侧),甚至有浮动也是如此(尽管盒子里的行盒子 Line Box 可能由于浮动而变窄),除非盒子创建了一个新的BFC(在这种情况下盒子本身可能由于浮动而变窄)。
因而,如果 p
元素创建一个新的BFC那它就不会再紧贴包含块的左侧了。
如果我们创建一个占满整个容器宽度的多列布局,在某些浏览器中最后一列有时候会掉到下一行。这可能是因为浏览器四舍五入了列宽从而所有列的总宽度会超出容器。但如果我们在多列布局中的最后一列里创建一个新的BFC,它将总是占据其他列先占位完毕后剩下的空间。
例如:
<div class="container">
<div class="column">column 1</div>
<div class="column">column 2</div>
<div class="column">column 3</div>
</div>
对应的CSS:
.column {
width: 31.33%;
background-color: green;
float: left;
margin: 0 1%;
}
/* Establishing a new block formatting
context in the last column */
.column:last-child {
float: none;
overflow: hidden;
}
现在尽管盒子的宽度稍有改变,但布局不会打破。当然,对多列布局来说这不一定是个好办法,但能避免最后一列下掉。这个问题上弹性盒或许是个更好的解决方案,但这个办法可以用来说明元素在这些环境下的行为。