重提CSS中外边距折叠问题

避免父子元素边距折叠问题的方式分两类:

  1. 为父元素创建块级格式化上下文(BFC)(有一些元素是默认创建了 BFC 的,比如 body 元素),让它的子元素的 margin 值不影响父元素的 margin 值计算
  2. 将父元素与它的第一子元素「隔离开」,比如给父元素加 paddingborder

但是当这个父元素是 body 元素的时候,奇怪的现象发生了,HTML 代码如下:

html<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <title>testBodyMargin</title>
</head>
<body>
    <div class="father">
        <div class="son"></div>
    </div>
</body>
</html>

CSS 代码如下

csshtml,
body,
div {
    margin: 0;
    padding: 0;
}
html, 
body {
    width: 100%;
    height: 100%;
}
html {
    background: #fff;
}
body {
    background: #a0cbed;
}

.father {
    overflow: hidden;
    width: 200px;
    height: 200px;
    margin: 50px;
    background: #008800;
}

.son {
    width: 50px;
    height: 50px;
    margin: 20px;
    background: #cc0000;
}

现象如图:

clipboard.png

此时 .fatherbody 元素的上外边距是折叠的,按照 BFC 的理论,如果 body 元素默认自动创建 BFC 的话, .fahter 元素的 margin-top 不应该影响到 body元素的外边距才对,这是疑问一

下面给 body 元素加一个 overflow 的 CSS 属性

cssbody {
    overflow: hidden;
    background: #a0cbed;
}

现象与上图无差。

此时 .fatherbody 元素的上外边距仍然是折叠的,难道文章开头提到的避免折叠的方式一对于 body 元素无效了,这是疑问二

在此基础上,再给 html 元素加一个 overflow 的 CSS 属性

csshtml {
    background: #fff;
    overflow: hidden;
}

奇迹发生了, 现象如图:

clipboard.png

.fatherbody 元素的上外边距不折叠了,这是疑问三

求大神答疑解惑一下,谢谢!

阅读 10.3k
1 个回答

一开始我还以为是chrome的bug,后来发现这个表现在现代浏览器上能稳定复现。

做几个实验:

1# body默认创建BFC吗?

假如body默认创建BFC,那么作为block formatting context roots,此时根据W3C CSS2.1 10.6.7的block formatting context roots高度计算原则,body的高度计算中将算入float元素的高(也就是中文互联网常说的“闭合浮动”)。

创建用例:body内包含一个float元素,观察body是否被其撑高:
http://jsfiddle.net/go03845p/

结果是没有:

图片描述

body在IE6/7下面默认有haslayout;但在遵循了CSS2.1标准的浏览器中,body并不具有默认创建BFC的特性。

回到LZ的用例1,由于body默认不创建BFC,因此不能阻止margin折叠。

2# body在什么时候能够创建BFC?

测试一下常规的BFC创建方式:

  1. 给body加上overflow:hidden
    http://jsfiddle.net/0yjxurfv/4/

结果:无法触发BFC的创建。

  1. 给body和html同时加上overflow:hidden
    http://jsfiddle.net/0yjxurfv/5/

结果:可以触发BFC的创建。

  1. 给body加上display:tabledisplay:inline-blockposition:absolute
    http://jsfiddle.net/0yjxurfv/6/

结果:可以触发BFC的创建。

为何呢?W3C CSS2.1中,BFC是这样被定义的

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

overflow:visible以外的块级元素将创建BFC,除非该值已经扩散到了视口
(大部分中文资料都没有译出这个except,直到这个问题被提出,我也没有意识到这个except适用于哪个场景)

再寻找overflow:hidden的标准

  1. UAs must apply the 'overflow' property set on the root element to the viewport.

  1. When the root element is an HTML "HTML" element or an XHTML "html" element, and that element has an HTML "BODY" element or an XHTML "body" element as a child, user agents must instead apply the 'overflow' property from the first such child element to the viewport, if the value on the root element is 'visible'.

  2. The 'visible' value when used for the viewport must be interpreted as 'auto'.

  3. The element from which the value is propagated must have a used value for 'overflow' of 'visible'.

按语序提取一下(这段颠三倒四颠鸾倒凤的话的)要点:

  1. UA需要将root元素上的overflow属性置于视口之上;

  2. overflow扩散行为:当root元素是html元素且overflowvisible,而且html元素有body作为其子元素,UA则需要将第一个body之上的overflow属性应用于视口;

  3. 用于视口的overflow: visible将被解析为overflow: auto

  4. overflow扩散行为将导致body的使用值为overflow: visible

我们可以解释本节的三个用例里发生的事情了:

  1. 给body加上overflow:hidden,无法触发BFC创建。
    解释:本用例中body {overflow:hidden} html {overflow: visible}(html为默认overflow),body的overflow:hidden被应用于视口,body的最终使用值为overflow:visible,因此body没有创建BFC。

  2. 给body和html同时加上overflow:hidden,成功触发BFC创建。
    解释:本用例中body, html{overflow:hidden},html的overflow:hidden被用于视口,body的overflow计算值是hidden,因此创建了BFC。

  3. 给body加上display:tabledisplay:inline-blockposition:absolute,成功触发BFC创建。
    解释:这些属性都导致body正常创建了BFC。

LZ的用例2的body没有创建BFC因此没有避免margin折叠,用例3的body成功创建了BFC因此避免了margin折叠。


最后,不要完全相信任何非一手的资料,尤其是中文资料。

推荐问题
宣传栏