Margin Collapse、BFC外加一点点“清除浮动”

建议在PC端阅读
本文面向对象:对标题中的概念基本不了解或仅仅听说过名字的人。如果已有一定了解请直接拉到最下看推荐阅读
另外其实这是个很大的话题,这里仅当抛砖引玉,并提供进一步阅读的文章

一些demo

我们先从一些现象开始入手吧↓(建议在另一窗口打开示例,一边修改示例一边阅读效果更佳)
本文示例

例一

第一个例子是最常见的一种边界折叠(Margin Collapse)。一般我们很容易忽略掉这个现象,因为它和我们大多数情况下的直觉比较符合。
例如第一个例子里的两个p标签,它们的关系是并列的,所以我们设置margin:20px 0;时心里其实是想让两个p标签之间相隔20px(事实上现在也是这样表现的)。
但有时候我们并不想让它们折叠(我们希望“修正”的同时,保持使用margin并尽量少添加难以理解的CSS属性),那要怎么做呢?(即需要清楚产生边界折叠的条件)

例子中提示的做法是用一个设置了overflow:hidden;的div将其包裹,但是overflow:hidden;作用明明是剪裁溢出的内容,为何能产生取消边界折叠的效果呢?直接给出这样的解决方案让人难以理解而且容易忘记。

(似乎只能通过外加BFC创造不同的BFC来避免...)

例二

如果说第一个例子一定程度上还算符合直觉,那第二个例子的情况老实说我是不能理解的...一度让我以为是出了bug...但了解了Margin Collapse的产生条件之后,相信你能明白为何会出现这种现象以及如果消除它。

例三

第三个例子主要和BFC有关。一般说到"清除浮动"就会想到clear:both; overflow:hidden;之类.但是给父元素设置浮动居然也能"清除浮动"???估计深受浮动折磨的人看到这现象会进一步崩溃:为什么这样也能清除浮动?到底有多少种清除浮动的方法?(当然“清除浮动”不是本文重点,若想进一步了解请看推荐阅读


所以这些现象到底跟标题中的Margin Collapse、BFC有什么关系呢?

上面例子的现象你都能给出解释和解决方案吗?如果都没问题的话恭喜你,可以直接下拉到最后看推荐阅读进行进一步阅读了。如果一头雾水的话也没关系,我们一步一步来看是怎么回事。

想一下,有没有觉得这有点像读英语时每个单词都认识,连在一起就看不懂的情况?这是因为缺乏相关的语法知识(当然也有可能是缺少上下文)
每一条CSS Rule就像一个个单词,而相应的语法就是w3c制定的布局方案了(当然这里的类比不完全准确,自然语言是与上下文有关的语法,而CSS是与上下文无关的语法)

那w3c是怎么规定Margin Collapse的呢?


产生折叠的必备条件:margin必须是邻接的!

而根据w3c规范,两个margin是邻接的必须满足以下条件:
1.必须是处于常规文档流(非float和绝对定位)的块级盒子,并且处于同一个BFC(BFC产生条件见下文)当中。
2.没有线盒,没有空隙(clearance,本文不涉及),没有padding和border将他们分隔开
3.都属于垂直方向上相邻的外边距,可以是下面任意一种情况

  • 元素的margin-top与其第一个常规文档流的子元素的margin-top(即例二)
  • 元素的margin-bottom与其下一个常规文档流的兄弟元素的margin-top(即例一)
  • height为auto的元素的margin-bottom与其最后一个常规文档流的子元素的margin-bottom
  • 高度为0并且最小高度也为0,不包含常规文档流的子元素,并且自身没有建立新的BFC的元素的margin-top和margin-bottom

以上的条件意味着下列的规则:

  • 创建了新的BFC的元素(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 会折叠。

折叠的结果:

两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
两个外边距一正一负时,折叠结果是两者的相加的和。

摘自w3plus: http://www.w3cplus.com/css/understanding-bfc-and-margin-collapse.html © w3cplus.com


BFC(Block Formatting Contexts,块级上下文)的产生条件

BFC在CSS3中改为Flow Root,触发条件多了下面加粗的部分

  • 浮动元素,float 除 none 以外的值
  • 绝对定位元素,position(absolute,fixed
  • display 为以下其中之一的值 inline-blocks,table-cells,table-captions
  • overflow 除了 visible 以外的值(hidden,auto,scroll)

注:"display:table" 本身并不产生 BFC,但它会产生匿名框,匿名框中包含 "display:table-cell" 的框会产成 BFC。


好的,上面一口气丢了一大堆定义和条件出来,估计直接强行读下来已经有点晕了。没关系我们结合文章开头的例子看一下吧。

例子解析

例一

显然例一符合折叠的第一个条件:

1.必须是处于常规文档流(非float和绝对定位)的块级盒子,并且处于同一个BFC(BFC产生条件见下文)当中。

那要取消折叠自然就是破坏第一个条件啦,于是第一反应就是用overflow:hidden;来把p标签变成BFC。然后你就会开心的发现......并没有什么变化。
为什么呢?我们再来读一下第一个条件

"....处于同一个BFC当中。"

嗯,这里要搞清楚一个概念,BFC是一个区域,不是一个元素。
也就是说,你给p标签设的overflow:hidden;只是把p标签里的内容设置成一个新的BFC,但是这个p标签仍处于它之前所在BFC中这个事实没有改变。
所以给出的解决方案是外面多包裹一个div再把这个div变成BFC(如overflow:hidden;),这样被包裹的p标签处于它的父元素创建的BFC,而它的父元素此时和另一个p标签处于同一个BFC,所以被包裹的p标签与另一个p标签自然不处于同一个BFC了(表达能力有限,这段写的有点绕)

虽然要多加一个标签,但其实仔细想想也没有破坏语义性。因为正是多加了一个标签才表示了被包裹的内容与外面的内容不处于同一个上下文。

例二

例二其实也是因为处于同一个BFC中而导致的,你可以试着给.parent加上overflow:hidden;看看效果。demo中让你尝试加border或padding主要是因为这个折叠条件比较容易忽略。
不过产生BFC的条件多多少少都会有副作用,而这里其实只要让子元素margin与父元素marigin不相邻(折叠的最基本要求)就可以了
这里推荐的解决方案是使用伪元素:

.fathet:before{
     content:"";
     display: table;
}

首先content是伪元素生成的必要条件
这里的原理是:display:table;会生成包含display:table-cell;的匿名框(见上文BFC产生条件),从而产生了一个“没有高度”的BFC区域(个人理解,如有错误欢迎指出)将子元素和父元素的margin分割开了。

例三

这里主要是因为BFC的特性所致
BFC有如下特性:

  1. BFC可以包含浮动元素

w3c原文:“'Auto' heights for block formatting context roots”,也就是 BFC 会根据子元素的情况自动适应高度,即使其子元素中包括浮动元素。

  1. BFC可以阻止元素被浮动元素覆盖(即取消文字环绕的效果)

所以例三给父元素设置浮动后高度不再坍塌只是因为父元素的产生了BFC,而用其他产生BFC的方法同样能包含浮动元素。
值得注意的是,虽然BFC有闭合浮动的效果,但并不推荐利用BFC闭合浮动。因为每一个用于产生BFC的条件都有一定程度的副作用,尽管当时可能副作用不明显,但也是个很大的隐患。
推荐的“清除浮动”的方法是Nicolas Gallagher大神提出的micro clearfix hack:

.clearfix:after{
  content:"";
  display:table;
  clear:both;
}

总结

  1. Margin Collapse不是bug,而是在CSS布局规则里面有规定的,具体出现情况见上文。
  2. BFC是一个区域,或者说是元素具有的一个特性,而不是元素本身。
  3. BFC能闭合浮动,但不应该用BFC来闭合浮动。

思考

先给body加上border,方便观察。
试着给body标签加上margin,再给body的第一个子元素(需要是块级的)设置margin,看是否会发生折叠?
如果会发生折叠,也就是符合条件1

1.必须是处于常规文档流(非float和绝对定位)的块级盒子,并且处于同一个BFC当中。

也就是处于同一个BFC中。这时如果给第一个子元素设置浮动又会发生什么呢?

实验的结果是body无法包含浮动元素,这与BFC的特性矛盾。但同时却能发生Margin Collapse,也就是处于同一个BFC中。这似乎有点矛盾。
当然我也没想通这是为什么,如果有明白的人欢迎指教。我能想到的就只有因为body标签太特殊,这是浏览器对它作出的特殊处理......


推荐阅读

四篇文章搞定BFC(建议依次阅读)

  1. 详说 Block Formatting Contexts (块级格式化上下文)
  2. W3C的官方定义
  3. Understanding Block Formatting Contexts in CSS
  4. Formatting contexts

BFC与Margin Collapse

深入理解BFC和Margin Collapse

搞定清除浮动

那些年我们一起清除过的浮动

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 10,704评论 1 91
  • 1.浮动元素有什么特征?对父容器、其他浮动元素、普通元素、文字分别有什么影响? 何谓浮动元素?有什么特征?所谓浮动...
    草鞋弟阅读 354评论 0 1
  • 浮动元素的特征,对父容器、对其他浮动元素、普通元素、文字的影响。 浮动会使应用的元素脱离文档流。按指定的位置来移动...
    邢烽朔阅读 330评论 2 7
  • 1.在什么场景下会出现外边距合并?如何合并?如何不让相邻元素外边距合并?给个父子外边距合并的范例 概念:在CSS当...
    饥人谷_任磊阅读 233评论 0 3
  • 在什么场景下会出现外边距合并?如何合并?如何不让相邻元素外边距合并?给个父子外边距合并的范例 在CSS当中,相邻的...
    Nicklzy阅读 423评论 0 51