谈谈BFC与ie特有属性hasLayout
最近看了一篇总结ie常见bug的文章,里面提到ie多数的bug源于她的特有属性:hasLayout。这个属性以前也了解过一点,但没有深入去理解,于是查阅了一些相关的资料,现在在此来对这个属性作一下总结。
一、hasLayout的定义。
这里我就不一一敲了,直接复制百度的咯。
在ie中,一个元素要么自己对自身的内容进行计算大小和组织,要么依赖于父元素来计算尺寸和组织内容。为了调节这两个不同的概念,渲染引擎采用了 hasLayout 的属性,属性值可以为true或false。当一个元素的 hasLayout属性值为true时,我们说这个元素有一个布局(layout)。
如果它设置成了true,它就不得不去渲染它自己,因此元素不得不扩展去包含它的流出的内容。例如浮动或者很长很长的没有截断的单词,如果haslayout没有被设置成true,那么元素得依靠某个祖先元素来渲染它。这就是很多的ie bugs诞生的地方。
<html>, <body>
<table>, <tr>, <th>, <td>
<img>
<hr>
<input>, <button>, <select>, <textarea>, <fieldset>, <legend>
<iframe>, <embed>, <object>, <applet>
<marquee>
激活“haslayout”的方式——调整下列css属性:
width:非auto任意值——优先考虑
- height:非auto任意值——对 IE6 及更早版本来说很常用,该方法被称为霍莉破解(Holly hack),即设定这个元素的高度为 1% (height:1%;)。但是要注意,当这个元素的 overflow 属性被设置为 visible 时,这个方法就失效了。
zoom:非normal任意值——该属性也为ie特有属性。一般测试的时候用zoom:1。可以避免改变其他属性破坏布局。
position:absolute——可能引发新问题。
- float:left/right——ie 常见bug很多都因为元素设置了浮动而触发haslayout产生的。
display:inline-block——当一个内联元素想获得layout就要使用这个属性。
min-height、max-height
(除none)
、min-width、max-width(除none)
设置任意值——针对ie7。- overflow、overflow-x、overflow-y除visible外任意值——针对ie7。
position:fixed——针对ie7。
重置“haslayout”:需要没有其他属性激活haslayout的前提下。
- width, height (设为 "auto")
- max-width, max-height (设为 "none")(在 IE 7 中)
- position (设为 "static")
- float (设为 "none")
- overflow (设为 "visible") (在 IE 7 中)
- zoom (设为 "normal")
- writing-mode (从 "tb-rl" 设为 "lr-t")
display 属性的不同:当用"inline-block"激活了haslayout 属性时,就算在一条独立的规则中覆盖这个属性为"block"或"inline",haslayout 这个标志位也不会被重置为 false。把 mid-width, mid-height 设为它们的默认值"0"仍然会赋予 hasLayout,但是 IE 7 却可以接受一个不合法的属性"auto"来重置 hasLayout。
二、haslayout具体作用
上一部分总结了haslayout的定义和激活条件,现在来到了我们最关心的问题:haslayout到底有什么用。要说haslayout的作用,我们先来看看BFC。
什么是BFC?BFC,全称即:Block Formatting Context,直译为:块级格式化范围。W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。当涉及到可视化布局的时候,Block Formatting Context提供了一个环境,HTML元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。
要更好地理解BFC,要先来谈谈Box和Formatting Context的概念。我们知道网页布局是由很多盒子组成的,这些块就是Box。元素的类型和 display 属性,决定了这个 Box 的类型。 不同类型的 Box, 会参与不同的 Formatting Context(一个决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染。
这些盒子有:
- block-level box:display 属性为 block, list-item, table 的元素,会生成 block-level box。并且参与 block fomatting context;
- inline-level box:display 属性为 inline, inline-block, inline-table 的元素,会生成 inline-level box。并且参与 inline formatting context;
而Formatting Context是一块渲染区域,它决定了其子元素如何定位,以及与其他元素的位置关系。
根据上述的一些基本概念,我把BFC简单理解成一个容器,在BFC这个容器中,元素按照BFC的规则实现布局。比如浮动元素会形成BFC,这就是为什么我们看到浮动元素布局跟普通文档流下的布局有所差别的原因。
BFC中的规则简单整理为以下几条:
- 内部的Box会在垂直方向,一个接一个地放置。
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
- 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
- BFC的区域不会与float box重叠。
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
- 计算BFC的高度时,浮动元素也参与计算
哪些元素会形成BFC:
- 根元素
- float属性不为none
- position为absolute或fixed
- display为inline-block, table-cell, table-caption, flex, inline-flex
- overflow不为visible
BFC在布局中的作用:
谈到作用,需要理解BFC中的布局规范。 第一条没什么好说的,就是按照正常文档流的渲染顺序对元素进行排布。
第二条是我们常说的margin重叠问题。要想两个相邻的元素不发生垂直方向上的margin重叠,需要将他们两定义在不同的BFC中。解决方法即在其中一个元素外包裹一层元素,再 对那层包裹的元素进行BFC触发。(这里可以加入上述的css属性。)
第三条意思是,在BFC包含块中,每一个元素都会与父盒子进行左对齐。如果元素具有左边的margin,那么则是元素的margin边界与父盒子的border边界对齐。这就是为什么我们 设置绝对定位时,所有具有绝对定位熟悉的元素初始化时都会以左上角重叠。
第四条跟我们的浮动布局有关。一般情况下,浮动元素会脱离文档流,即不占位置。它的兄弟元素会与它在左上角重叠。但是如果两个相邻元素都设置了浮动,那么意味着它们都是 以BFC的规则渲染,根据第四条,BFC区域不会相互重叠,所以便能理解为什么设置浮动后元素能独占空间了。
第五条不是特别理解,占且放一下。
第六条也是特别常见。在普通容器中,如果里面有浮动元素,在不设置高度的情况下,容器是不能被撑起来的,这时候通过设置overflow:hidden把其变为BFC,那么就可以包含浮 动元素了。
BFC的说明到此就告一段落了,现在回到最初讨论的haslayout的问题。ie7及以下ie版本不支持BFC的,但有私有属性haslayout,于是我们可以通过触发元素的haslayout来达成bfc的相似效果。
三、IE下因为haslayout导致的bug
1、浮动元素与普通元素之间产生3px bugcss:
.test {width: 800px;margin: 10px auto;border: 1px solid brown;height: 30px;}
.float {float: left;background: saddlebrown;color: #fff;}
html:
<div class="test">
<div class="float">我是浮动元素</div>我是后面的文字,用来测试3px的bug
</div>
正常情况:ff下:
ie6下:
解决方式:加一个ie6的hack:*margin-right:-3px;
不只是文字,ie6的浮动元素也会和内联元素产生3px的margin值。
正常情况:ff下:
ie6下:
解决方式同上。
2、块级元素与浮动元素不能重叠
css:
.test {width: 800px;margin: 10px auto;border: 1px solid brown;height: 30px;}
.float {float: left;background: saddlebrown;color: #fff;}
html:
<div class="test">
<div class="float">我是浮动元素</div>
<div style="background: #0079F5;height: 30px;">我是浮动元素后面的块级元素</div>
</div>
正常情况:ff下:
ie6下:
可以明显地发现ie6下块级元素跟浮动元素不能重叠。为什么会发生这种情况呢?是因为我在块级元素上设置了高度。激活了ie下的haslayout属性。于是ie6把它以BFC类似的方式进行渲染。
解决方式:在块级元素外再包裹一层DIV。并且把内部DIVbackground的属性写在外层DIV上。
3、浮动闭合元素
这个问题其实很多人会遇到,上文也提到过,只是可能不知道它的名字。
css:
.test {width: 800px;margin: 10px auto;border: 1px solid brown;}
.float {float: left;background: saddlebrown;color: #fff;}
html:
<div class="test">
<div class="float">我浮动啦!</div>
</div>
正常情况下:ff下:
注意那一条横线是.test的border,因为浮动元素脱离了文档流,故.test不能被撑起来。
ie下:
ie似乎妥妥的。其实很多情况下,我们想要的是ie这种效果。在ie中,一个浮动元素总是隶属于包含它的容器。是因为.test设置了宽度,激活了haslayout属性。而在非ie浏览器中,我们想要获得这种效果一般是在父盒子上加一个:after的伪对象来清除浮动,或者设置overflow:hidden来触发BFC。
4、ie下margin不塌陷
css:
.test {width: 800px;margin: 10px auto;}
.float {float: left;background: saddlebrown;color: #fff;}
html:
<div class="test">
<div class="float">我浮动啦!</div>
<div style="margin-top:30px;">测试margin-top在ie下是否塌陷</div>
</div>
正常情况:ff下:
ie下:
.float是浮动元素,她脱离了文档流,所以第二个DIV的margin-top相对的是其上级.test作用的。但我们只是对第二个DIV设置margin-top。结果在FF下,怎么连float也“产生了margin-top”呢。对比ie和FF下的效果,是不是觉得IE下的 解析会比较合理呢?
但是。别忘了影响margin-top/bottom的一个重要规则——margin塌陷(margin collapsing)。
提到了margin塌陷,我们来看看margin垂直方向上塌陷的一些条件:
1. 水平margin不会合并。
2. 两个上下渲染相邻(不一定是兄弟节点)的块状元素在正常页面流情况下会发生 margin 合并。
3. 浮动元素不会和任何元素(包括子孙节点)发生 margin 合并。
4. overflow!=visible的元素不和任何元素发生margin合并。
5. 绝对定位的元素不和任何元素发生margin合并。
6. inline-block 的元素不和任何元素发生margin合并。
7. 设置 clear 属性的元素不和任何元素发生margin合并。
8. 根元素不和任何元素发生margin合并。
9. 父节点和第一个子节点发生margin-top合并。
10. 如果最后一个子节点没有border以及padding,则和其父节点发生margin-bottom合并。
注意IE!特别是hasLayout对于margin合并也有影响,从而也造成了包含的绝对定位元素的位置差异。
在ff下.test块的高度并没有被子元素第二个DIV的margin-top撑开。反而自身拥有了30px的margin-top。
而浮动的left尽管脱离了文档流,但还是受其父级限制的(这跟absolute定位的元素层受限于其定义为relative的父级一样)。所以float还是包含在test之中,这样在ff下看起来left也拥有margin-top,而事实上是因为test高度不撑开的结果。
这么说,FF并没有错咯,那么IE下又是怎么避开margin塌陷的呢?问题就出在浮动上面,在ie下,元素浮动将触发其haslayout。就是这个原因,使得在ie下意外(意外?)的就避开了margin塌陷。
但是奇怪的是,如果给.test加上border,ff的渲染情况就跟ie一样了,float没有拥有与第二个DIV相同的margin-top值。而是紧贴.test的顶部。如下图:
ff:
ie:
这个问题的原因还需要研究一下。。。。如果有知道原因的大神麻烦给博主留个言,,感激!
5、ie下margin-left/right失效
css:
.test {margin: 10px auto;border:1px solid darkred;}
html:
<div class="test">
<div style="height:30px;margin:0 20px;border-bottom: 3px solid sandybrown;">测试失效的margin-left</div>
</div>
正常情况:ff下:
ie下:
根据上图可以发现,ie下我们为子DIV设置的margin失效了。为什么会发生这种情况,是因为子DIV设置了高度,激活了haslayout。
解决办法:不为子DIV设置高度,或者把父盒子的haslayout也激活。
参考资料:http://cssass.com/blog/2009/147.html
http://www.cnblogs.com/lhb25/p/inside-block-formatting-ontext.html
http://www.cnblogs.com/pigtail/archive/2013/01/23/2871627.html