CSS的两种格式化上下文:BFC和IFC
CSS的两种格式化上下文
文章包含很多个人理解,如果错误,欢迎指出~
在看本文之前,你要对CSS的盒子模型,Block-Level元素,Inline-Level元素有所了解,具体可参考CSS的盒子模型、三种元素类型。
本文具体解读了CSS针对block-level元素和inline-level元素设计的两种格式化上下文:BFC(Block Formatting Context)和IFC(Inline Formatting Context),它们规定了block-level元素和inline-level元素在对应格式化上下文中的渲染规则。了解上述内容是清楚理解网页排版的基础,当然在此基础上,还要进一步掌握CSS的几种定位方法,浮动等内容,才能理解和设计更加复杂的网页布局。
格式化上下文
格式化上下文即Formatting context,它是指页面上的一个局部独立渲染区域,根据Formatting context中包含的是元素类型的不同,分为块级格式上下文BFC和行内格式化上下文IFC,不同的格式化上下文对应着不同的渲染规则,来告诉页面多个Block-Level元素或是多个Inline-Level元素在页面中该如何布局。需要注意,在BFC中只会包含Block-Level元素,同样的,在IFC中只会包含Inline-Level元素。
块级格式化上下文BFC(Block Formatting context)
触发BCF的方法,下面这些元素都会产生一个新的BFC
- 根元素;
float
属性不为none;position
为absolute或fixed;display
为inline-block, table-cell, table-caption, flex, inline-flex;overflow
不为visible;
BFC包括如下特性
-
BFC内部的Block-Level元素会在垂直方向,一个接一个地放置;
-
两个相邻的处于同个BFC的Block-Level元素的margin会发生重叠;这里包括相邻的兄弟元素和嵌套元素;发生重叠的具体条件,和重叠的方式,请看http://www.cnblogs.com/ningyn0712/p/5369024.html
-
BFC内部的Block-Level元素的left outer edge与它的container Box的left inner edge重合;
-
BFC不会与它同级的float元素发生重叠;
利用这个特性可以很方便的实现动态两栏的结构。对于处于同一个BFC中Block-Level元素和一个float元素,它们是会发生重叠的,如Example2所示;但如果我们将Block-Level元素触发成一个新的BFC,这个Block-Level元素就会自动通过缩小box宽度避免float元素重叠,如Example 3所示;但是实验发现,通过设置overflow实现的BCF不能完全避免重叠,这个Block-Level元素的margin区域还是会与float元素发生重叠,如Example 4所示;希望有人可以帮忙解释原因;
Example 2: 在id为container-div的div标签中放置一个id为th1-div的浮动div标签和一个id为th2-div的普通标签;
<style type="text/css"> #container-div { background: cornflowerblue; height: 200px; } #th1-div { float: left; height: 100px; width: 100px; background: red; } #th2-div { height: 100px; background: orange; } </style> <div id="container-div"> <div id="th1-div"> </div> <div id="th2-div"> </div> </div>
**Example 3** :在id为container-div的div标签中放置一个id为th1-div的浮动div标签和一个id为th2-div的普通标签;通过设置#th2-div的overflow: hidden;来触发#th2-div的BFC,以达到#th1-div与#th2-div不重叠的目的;
<style type="text/css">
#container-div
{
background: cornflowerblue;
height: 200px;
}
#th1-div
{
float: left;
height: 100px;
width: 100px;
background: red;
margin-right: 10px;/*为了清楚看出两个div是分离的*/
}
#th2-div
{
height: 100px;
background: orange;
overflow: hidden;/*触发BFC*/
}
</style>
<div id="container-div">
<div id="th1-div">
</div>
<div id="th2-div">
</div>
</div>
![](https://i.imgur.com/FBFh3bH.png)
**Example 4**: 根据Example 3的方法触发#th2-div的BFC,同时设置#th2-div的margin-left: 10px;按理说,因为BFC特性,#th1-div与#th2-div应该不会发生重叠,结果应该与Example 3的图一样,但是实验发现,#th2-div的margin区域和#th1-div是重叠的,如下图所示;
<style type="text/css">
#container-div
{
background: cornflowerblue;
height: 200px;
}
#th1-div
{
float: left;
height: 100px;
width: 100px;
background: red;
/*margin-right: 10px;*/
}
#th2-div
{
height: 100px;
background: orange;
margin-left: 10px;
overflow: hidden;/*触发BFC*/
}
</style>
<div id="container-div">
<div id="th1-div">
</div>
<div id="th2-div">
</div>
</div>
![](https://i.imgur.com/8kYYz42.png)
-
BFC就是一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
利用这个特性,我们可以解决父子元素margin重叠的问题。根据BFC特性2我们知道,在同一个BFC里面嵌套的父子Block-Level元素的margin区域是会发生重叠的,如Example 5所示;但如果我们触发父元素生成一个新的BFC,那么它的子元素就不再和父元素处于同一个BFC,从而解决父子元素margin重叠的问题,如Example 6所示。
Example 5: 在Example 5中,#container-div创建了一个新的BFC,#container-div嵌套着#th1-div,#th1-div嵌套着#th2-div,同时#th1-div设置了margin-top: 10px,#th2-div设置了margin-top: 20px;我们可以看到最终#th1-div和#th2-div基于border-top对齐,两者margin-top重叠;
<style type="text/css"> #container-div { background: cornflowerblue; height: 300px; overflow: hidden;/*触发BFC*/ } #th1-div { height: 200px; width: 200px; background: green; margin-top: 10px; } #th2-div { height: 100px; width: 100px; background: orange; margin-top: 20px; } </style> <div id="container-div"> <div id="th1-div"> <div id="th2-div"> </div> </div> </div>
Example 6: 在Example 5的基础上,我们通过设置overflow: hidden;让#th1-div创建了属于一个BFC,从而让#th1-div和#th2-div不再属于同一个BFC,结果显示#th1-div和#th2-div的margin区域不再重叠,它们成功以我们希望的看到的布局展示。
<style type="text/css"> #container-div { background: cornflowerblue; height: 300px; overflow: hidden;/*触发BFC*/ } #th1-div { height: 200px; width: 200px; background: green; margin-top: 10px; overflow: hidden;/*触发BFC*/ } #th2-div { height: 100px; width: 100px; background: orange; margin-top: 20px; } </style> <div id="container-div"> <div id="th1-div"> <div id="th2-div"> </div> </div> </div>
-
计算BFC的高度时,浮动元素也参与计算;
利用这个特性,我们可以解决浮动元素造成的父元素塌陷问题。 如Example 7所示,默认情况下,浮动元素不会撑开父元素的高度;但是当我们触发了父元素的BFC,如EXample 8所示,这时父元素在计算高度时,会把浮动元素也计算在内,也就解决了父元素塌陷的问题。
Example 7: 在#th1-div里面放入一个左浮动的#th2-div元素,因为浮动元素脱离了文档流,所以#th2-div元素无法撑开#th1-div的高度,如图所示;
<style type="text/css"> #container-div { background: cornflowerblue; height: 300px; overflow: hidden;/*触发BFC*/ } #th1-div { width: 200px; background: green; } #th2-div { height: 100px; width: 100px; background: orange; float: left; } </style> <div id="container-div"> <div id="th1-div"> <div id="th2-div"> </div> </div> </div>
Example 8: 在Example 8的基础上,通过设置overflow: hidden;触发#th1-div的BCF,使得#th1-div在计算高度的时候会把浮动元素也计算进去,因此#th1-div的高度就变大了。
<style type="text/css"> #container-div { background: cornflowerblue; height: 300px; overflow: hidden;/*触发BFC*/ } #th1-div { width: 200px; background: green; } #th2-div { height: 100px; width: 100px; background: orange; float: left; } </style> <div id="container-div"> <div id="th1-div"> <div id="th2-div"> </div> </div> </div>
行内格式化上下文IFC(Inlinel Formatting context)
在了解IFC之前,必须对Inline-Level元素的Inline-box概念有所了解,需要知道Inline-Level元素在行中的占位是由Inline-box确定的,而非Inline-Level元素的盒子模型确定。同时你需要知道line box的概念,它其实就是包含多个处于一行的Inline-Level元素的行框,多个连续的Inline-Level元素按照从左到右,从上大小的顺序被放置在多个line boxs中。
IFC所规定的渲染规则其实就是对Inline-Level元素在line box中布局的几个关键问题进行了解答,具体如下:
-
多个连续的Inline-Level元素是怎样被拆分到多个line boxs中的?
我的理解是:IFC会先将所有的元素一个个依次首尾相接排列到一起,合并成一个完整的流,然后再确定流中不可拆分的部分,最后将这个流拆分布局到多个不定宽度的line boxs。在上述这个由多个元素组成的流中,不可拆分的部分包括没有空格的连续字符串、单个英文或其他字符、可替换的Inline-Level元素。
为什么要强调先要合并成完整的流这一过程,主要是想说明在IFC中不是以元素作为最小不可拆分单元的,元素里面的内容可能也可以拆分成多个部分布局到多个line boxs中,比如Example 9,元素之间也可能不可拆分,比如Example 10。
Example 9:当一个span里面的内容大于line box的宽度,它的内容自动拆分成两个部分,分布在两行(两个line box)中;
<style type="text/css"> #container-div { width: 100px; height: 200px; background: cornflowerblue; } span:nth-child(1)/*伪类选择器,选择span标签,同时它要是其父元素的第一个子元素*/ { color: orange; background:rebeccapurple; } span:nth-child(2) { color: green; background: goldenrod; } </style> <div id="container-div"> <span >This is a span</span> </div>
Example 10:两个span元素的宽度总和已经大于line box的宽度了,但是第二个元素没有进行换行布局。这是为什么,把它们想成一个整体就好理解了,因为第一个span里面的字符串和第二个span里面的字符串之间不存在空格,因此IFC把他们的内容理解成一个连续的字符串,他们也就成了不可拆分的整体,第二个span也就没有办法进行换行了。
<style type="text/css"> #container-div { width: 100px; height: 200px; background: cornflowerblue; } span:nth-child(1)/*伪类选择器,选择span标签,同时它要是其父元素的第一个子元素*/ { color: orange; background:rebeccapurple; } span:nth-child(2) { color: green; background: goldenrod; } </style> <div id="container-div"> <span >ItIsSpan1</span><span>ItIsSpan2</span> </div>
-
line box的宽度和高度由什么决定的?
line box的宽度由包含块和浮动元素决定:当不存在浮动元素时,line box的宽度等于包含块的内容区域的宽度;否则,因为Inline-Level元素会环绕浮动元素布局的特性,line box的宽度会比包含块的内容区域的宽度小。
line box的高度是由它包含的所有元素的Inline-box决定的,line box的上边界由行中最高的Inline-box上边界确定,line box的下边界由行中最低的Inline-box下边界确定;需要注意不要误以为line box的高度是由行内最高的元素决定;
-
Inline-Level元素在line boxs中的水平布局由什么决定的?
如果line box内部内所有Inline-box的总宽度小于line box的宽度,它们在line box中的布局由父元素的
text-align
属性决定;所以通过设置text-align
属性可以实现在父元素中居中的效果,如Example 11所示;Example 11:通过设置父元素的
text-align:center
就能实现行内元素居中效果。<style type="text/css"> #container-div { width: 100px; height: 200px; background: cornflowerblue; text-align: center;/*设置内容水平居中*/ } span:nth-child(1)/*伪类选择器,选择span标签,同时它要是其父元素的第一个子元素*/ { color: orange; background:rebeccapurple; } span:nth-child(2) { color: green; background: goldenrod; } </style> <div id="container-div"> <span >A span</span> </div>
-
Inline-Level元素在line boxs中的垂直布局由什么决定的?
Inline-Level元素在line box中的垂直位置由元素的
vertical-align
属性决定; -
最后一点,line box之间不会存在空隙,也不会发生重叠;
参考资料:
[2] 深入理解BFC