CSS: 深入理解BFC和Margin Collapse (margin叠加或者合并外边距)
BFC的理解与应用
首先我们来看看w3c规范对BFC的解释,其实对于这种概念的学习上,我们总是建议首先寻找官方的定义,因为原则上来说官方的才是最权威和正确的,而且还比较详细,千万不要因为看到英文就畏惧不前。
什么是BFC(Block formatting contexts)
w3c规范中的BFC定义:
浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不为“visiable”的块级盒子,都会为他们的内容创建新的BFC(块级格式上下文)。
在BFC中,盒子从顶端开始垂直地一个接一个地排列,两个盒子之间的垂直的间隙是由他们的margin 值所决定的。在一个BFC中,两个相邻的块级盒子的垂直外边距会产生折叠。
在BFC中,每一个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说,则触碰到右边缘)。
BFC的通俗理解:
首 先BFC是一个名词,是一个独立的布局环境,我们可以理解为一个箱子(实际上是看不见摸不着的),箱子里面物品的摆放是不受外界的影响的。转换为 BFC的理解则是:BFC中的元素的布局是不受外界的影响(我们往往利用这个特性来消除浮动元素对其非浮动的兄弟元素和其子元素带来的影响。)并且在一个 BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
BFC的运用
在w3c的规范中,除了上面的一段定义之外,BFC的相关知识点分布地比较零散,但基本集中在float、绝对定位、margin collaspe中。下面我们来看看如何应用到BFC来解决问题。
在很多网站中,我们经常会看到这样的一种,左边图片+右边信息的两栏结构,下面我们来看看如何利用BFC来实现。
首先我们给出这样的结构:
<style> .box {width:210px;border: 1px solid #000;float: left;} .img {width: 100px;height: 100px;background: #696;float: left;} .info {background: #ccc;color: #fff;} </style> <div class="box"> <div class="img">image</div> <p class="info">信息信息信息信息信息信息信息信息信息信息信息信</p> </div>
一般情况下它呈现出我们所乐意看到的样子:
但随着文字信息增多后,会变地非常的糟糕:
很 明显,这是因为info类里面的文字受到了浮动元素的影响,但这并不是我们所期望的。此时我们可以为P元素的内容建立一个BFC,让其内容消除对 外界浮动元素的影响。根据上文所知,只要给info元素添加overflow:hidden;即可为其内容建立新的BFC。当然你也可以通过其他方法来建 立。其效果如下:
合并外边距与BFC(或者 margin叠加)
在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。
折叠的结果:
- 两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
- 两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
- 两个外边距一正一负时,折叠结果是两者的相加的和。
产生折叠的必备条件:margin必须是邻接的!
而根据w3c规范,两个margin是邻接的必须满足以下条件:
- 必须是处于常规文档流(非float和绝对定位)的块级盒子,并且处于同一个BFC当中。
- 没有线盒,没有空隙(clearance,下面会讲到),没有padding和border将他们分隔开
- 都属于垂直方向上相邻的外边距,可以是下面任意一种情况
- 元素的margin-top与其第一个常规文档流的子元素的margin-top
- 元素的margin-bottom与其下一个常规文档流的兄弟元素的margin-top
- height为auto的元素的margin-bottom与其最后一个常规文档流的子元素的margin-bottom
- 高度为0并且最小高度也为0,不包含常规文档流的子元素,并且自身没有建立新的BFC的元素的margin-top和margin-bottom
以上的条件意味着下列的规则:
- 创建了新的BFC的元素(例如浮动元素或者'overflow'值为'visible'以外的元素)与它的子元素的外边距不会折叠
- 浮动元素不与任何元素的外边距产生折叠(包括其父元素和子元素)
- 绝对定位元素不与任何元素的外边距产生折叠
- inline-block元素不与任何元素的外边距产生折叠
- 一个常规文档流元素的margin-bottom与它下一个常规文档流的兄弟元素的margin-top会产生折叠,除非它们之间存在间隙(clearance)。
- 一个常规文档流元素的margin-top 与其第一个常规文档流的子元素的margin-top产生折叠,条件为父元素不包含 padding 和 border ,子元素不包含 clearance。
- 一 个 'height' 为 'auto' 并且 'min-height' 为 '0'的常规文档流元素的 margin-bottom 会与其最后一个常规文档流子元素的 margin-bottom 折叠,条件为父元素不包含 padding 和 border ,子元素的 margin-bottom 不与包含 clearance 的 margin-top 折叠。
- 一个不包含border-top、 border-bottom、padding-top、padding-bottom的常规文档流元素,并且其 'height' 为 0 或 'auto', 'min-height' 为 '0',其里面也不包含行盒(line box),其自身的 margin-top 和 margin-bottom 会折叠。
(下面我们对不产生折叠的情况逐一分析。)
浮动和绝对定位不与任何元素产生 margin 折叠
原因:浮动元素和绝对定位元素不与其他盒子产生外边距折叠是因为元素会脱离当前的文档流,违反了上面所述的两个margin是邻接的条件同时,又因为浮动和绝对定位会使元素为它的内容创建新的BFC,因此该元素和子元素所处的BFC是不相同的,因此也不会产生margin的折叠。
DEMO:
<style> body {padding:0;margin: 0; text-align: center;} .wrapper {margin:30px;width: 450px;border:1px solid red;} .small-box {width: 50px;height: 50px;margin: 10px;background: #9cc;} .middle-box {width: 100px;height: 100px;margin: 20px;background: #99c;} .big-box {width: 120px;height: 120px;margin: 20px;background: #33e;} .floatL {float: left;} .floatR {float: right;} .clear {clear: both;} .posA {position: absolute;} .overHid{overflow: hidden;} .red {background: #f00;} .green {background: #0f0;} .blue {background: #00f;} </style> <div class="wrapper overHid"> <div class="big-box blue">non-float</div> <div class="middle-box green floatL"> <div class="small-box red"></div> float left </div> </div>
但是浮动元素脱离了当前的BFC并不影响它后面的兄弟元素,后面的兄弟元素与浮动元素前面的元素依然在同一个BFC当中,所以,它们之间的margin还是会折叠的。下面我们对上面的demo做一下修改:
<div class="wrapper overHid"> <div class="big-box">non-float</div> <div class="middle-box green floatL">float left</div> <div class="middle-box red">non-clear</div> </div>
从上面这个修改后的demo中可以看出,红色的块盒在没有清楚浮动的情况下,它的margin-top和蓝色块盒的margin-bottom产生了折叠,这证明了我上面的结论。
下面我们来谈谈 'clearance' 这个神奇的东西,当浮动元素之后的元素设置clear以闭合相关方向的浮动时,根据w3c规范规定,闭合浮动的元素会在其margin-top以上产生一 定的空隙(clearance,如下图),该空隙会阻止元素margin-top的折叠,并作为间距存在于元素的margin-top的上方。关于这个间 距的计算稍微有点复杂,但实际工作中你并不需要去计算它,我们先来看看例子吧:
<div class="wrapper overHid"> <div class="big-box" style="box-shadow:0 20px 0 rgba(0,0,255,0.2);">non-float</div> <div class="middle-box green floatL" style="opacity:0.6">float left</div> <div class="middle-box red clear" style="margin-top:40px;box-shadow:0 -40px 0 rgba(255,0,0,0.2);">clear</div> </div>
上 面的图中我们可以看到,我们为红色块盒设置的40px的margin-top(这里我们通过相同高度的阴影来将其可视化)好像并没有对紫色块盒起 作用,而且无论我们怎么修改这个margin-top值都不会影响红色块盒的位置,而只由绿色块盒的margin-bottom所决定。
也就是说,我们只需要知道,闭合浮动的元素的border-top会紧贴着相应的浮动元素的margin-bottom。
原 来,通过w3c的官方规范可知,闭合浮动的块盒在margin-top上所产生的间距(clearance)的值与该块盒的margin-top 之和应该足够让该块盒垂直的跨越浮动元素的margin-bottom,使闭合浮动的块盒的border-top恰好与浮动元素的块盒的margin- bottom相邻接。
用上图例子中的相关值可以得出这样一个式子:r-margin-top + r-clearance = g-margin-top + g-height + g-margin-bottom
PS!闭合浮动并不能使浮动元素回到原来的BFC当中!
分析二:inline-block元素与其兄弟元素、子元素和父元素的外边距都不会折叠(包括其父元素和子元素)
inline-block不符合w3c规范所说元素必须是块级盒子的条件,因为规范中又说明,块级盒子的display属性必须是以下三种之一:'block', 'list-item', 和 'table'。
参考资料
- http://www.w3.org/TR/CSS2/box.html
- http://www.w3.org/TR/CSS2/visuren.html#block-formatting
- http://www.w3.org/TR/CSS2/box.html#collapsing-margins
出处:http://www.w3cplus.com/css/understanding-bfc-and-margin-collapse.html
总结margin的重叠(或合并外边距)
就参照标准的文档,结合自己的理解稍微翻译了一下,有些名词还是用英文描述比较合适。文档地址:点这里
在CSS中,两个或者更多的盒子的毗邻的外边距会重叠在一起,形成一个单一的外边距。外边距的这种组合方式我们称之为“坍塌”,最后形成的外边距我们称之为“坍塌的外边距(collapse margin)”。
垂直方向上的外边距的坍塌,除了:
- 根元素的盒子(the root elements’s box)的外边距永远不会坍塌。当你为
html
标签加上margin
时,无论有无毗邻的外边距,这个margin的都不会坍塌,是多少就多少。 - 如果一个拥有了“空隙”(clearance)的元素上下外边距毗邻,它的外边距将会与后面的兄弟元素的相邻外边距发生坍塌,但是发生坍塌后形成的外边距不会与父盒子的底外边
水平方向的外边距永远不会坍塌。
什么情况下两个外边距才算是毗邻(adjoining)呢?有且只有在下面的情况中:
- 同一个块级格式化上下文(block formatting context)中未脱离正常流(in-flow)中的块级盒子的外边距;
- 两个外边距之间不存在 line box(不包括高度确定无疑为零的 line box),没有 clearance,外边距和边框;
- 垂直相邻的的盒子之间的外边距,可以下面的任一种情况:
元素的margin-top
与其第一个正常流(in-flow)的子元素的margin-top
;
元素的margin-top
与其第一个正常流的子元素的margin-top
;
height为auto的元素的margin-bottom
与其最后一个正常流的子元素的margin-bottom
;
高度为0并且最小高度也为0,不包含正常流的子元素,并且自身没有建立新的BFC的元素的margin-top
和margin-bottom
;
形成坍塌的外边距的元素可以使非相邻元素或者祖先元素。
上面说到的也就意味着:
- 一个浮动的盒子与其他的盒子不会发生外边距坍塌,即使是浮动盒子和它内部的正常流中的孩子们;
- 自身建立了新的BFC的元素的外边距不会与它的处于正常流的孩子们的外边距重叠;
- inline-block 盒子的外边距不会坍塌,即使是它内部的正常流中的孩子们;
- 一个正常流元素的
margin-bottom
与它下一个正常流的兄弟元素的margin-top
会产生折叠,除非它们之间存在间隙(clearance)。 - 一个正常流元素的
margin-top
与其第一个正常流的子元素的margin-top
产生折叠,条件为父元素不包含padding
和border
,子元素不包含 clearance。 - 一个 ‘height’ 为 ‘auto’ 并且 ‘min-height’ 为 ‘0’的正常流元素的
margin-bottom
会与其最后一个正常流子元素的margin-bottom
折叠,条件为父元素不包含padding
和border
,子元素的margin-bottom
不与包含 clearance 的margin-top
折叠。 - 一个不包含
border-top
、border-bottom
、padding-top
、padding-bottom
的正常流元素,并且其 ‘height’ 为 0 或 ‘auto’, ‘min-height’ 为 ‘0’,其里面也不包含行盒(line box),其自身的margin-top
和margin-bottom
会折叠。