避免父子元素边距折叠问题的方式分两类:
- 为父元素创建块级格式化上下文(BFC)(有一些元素是默认创建了 BFC 的,比如
body
元素),让它的子元素的margin
值不影响父元素的margin
值计算 - 将父元素与它的第一子元素「隔离开」,比如给父元素加
padding
或border
但是当这个父元素是 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 代码如下
css
html, 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; }
现象如图:
此时 .father
和 body
元素的上外边距是折叠的,按照 BFC 的理论,如果 body
元素默认自动创建 BFC 的话, .fahter
元素的 margin-top
不应该影响到 body
元素的外边距才对,这是疑问一
下面给 body
元素加一个 overflow
的 CSS 属性
css
body { overflow: hidden; background: #a0cbed; }
现象与上图无差。
此时 .father
和 body
元素的上外边距仍然是折叠的,难道文章开头提到的避免折叠的方式一对于 body
元素无效了,这是疑问二
在此基础上,再给 html
元素加一个 overflow
的 CSS 属性
css
html { background: #fff; overflow: hidden; }
奇迹发生了, 现象如图:
.father
和 body
元素的上外边距不折叠了,这是疑问三
求大神答疑解惑一下,谢谢!
一开始我还以为是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创建方式:
给body加上
overflow:hidden
http://jsfiddle.net/0yjxurfv/4/
结果:无法触发BFC的创建。
给body和html同时加上
overflow:hidden
http://jsfiddle.net/0yjxurfv/5/
结果:可以触发BFC的创建。
给body加上
display:table
、display:inline-block
、position:absolute
http://jsfiddle.net/0yjxurfv/6/
结果:可以触发BFC的创建。
为何呢?W3C CSS2.1中,BFC是这样被定义的:
overflow:visible
以外的块级元素将创建BFC,除非该值已经扩散到了视口。(大部分中文资料都没有译出这个except,直到这个问题被提出,我也没有意识到这个except适用于哪个场景)
再寻找
overflow:hidden
的标准: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'.
The 'visible' value when used for the viewport must be interpreted as 'auto'.
The element from which the value is propagated must have a used value for 'overflow' of 'visible'.
按语序提取一下(这段颠三倒四颠鸾倒凤的话的)要点:
UA需要将root元素上的
overflow
属性置于视口之上;overflow扩散行为:当root元素是
html
元素且overflow
为visible
,而且html
元素有body
作为其子元素,UA则需要将第一个body
之上的overflow
属性应用于视口;用于视口的
overflow: visible
将被解析为overflow: auto
overflow扩散行为将导致
body
的使用值为overflow: visible
我们可以解释本节的三个用例里发生的事情了:
给body加上
overflow:hidden
,无法触发BFC创建。解释:本用例中
body {overflow:hidden} html {overflow: visible}
(html为默认overflow),body的overflow:hidden
被应用于视口,body的最终使用值为overflow:visible
,因此body没有创建BFC。给body和html同时加上
overflow:hidden
,成功触发BFC创建。解释:本用例中
body, html{overflow:hidden}
,html的overflow:hidden
被用于视口,body的overflow
计算值是hidden
,因此创建了BFC。给body加上
display:table
、display:inline-block
、position:absolute
,成功触发BFC创建。解释:这些属性都导致body正常创建了BFC。
LZ的用例2的
body
没有创建BFC因此没有避免margin折叠,用例3的body
成功创建了BFC因此避免了margin折叠。最后,不要完全相信任何非一手的资料,尤其是中文资料。