谈谈BFC与ie特有属性hasLayout

yvonne

发现我好久没更新博文了=-=这里把我之前在博客园写过的一篇关于BFC的文章粘贴过来,顺便自己也再次做个总结。


最近看了一篇总结ie常见bug的文章,里面提到ie多数的bug源于她的特有属性:hasLayout。这个属性以前也了解过一点,但没有深入去理解,于是查阅了一些相关的资料,现在在此来对这个属性作一下总结。

haslayout的普遍定义

洋洋洒洒一大篇。这里的内容如果觉得不好懂的话建议可以先看看后文提到的BFC属性。

在ie中,一个元素要么自己对自身的内容进行计算大小和组织,要么依赖于父元素来计算尺寸和组织内容。为了调节这两个不同的概念,渲染引擎采用了
hasLayout 的属性,属性值可以为true或false。当一个元素的
hasLayout属性值为true时,我们说这个元素有一个布局(layout)。

如果它设置成了true,它就不得不去渲染它自己,因此元素不得不扩展去包含它的流出的内容。例如浮动或者很长很长的没有截断的单词,如果haslayout没有被设置成true,那么元素得依靠某个祖先元素来渲染它。这就是很多的ie
bugs诞生的地方。

当一个元素有一个布局时,它负责对自己和可能的子孙元素进行尺寸计算和定位。简单来说,这意味着这个元素需要花更多的代价来维护自身和里面的内容,而不是依赖于祖先元素来完成这些工作。因此,一些元素默认会有一个布局。当我们说一个元素“拥有layout”或 “得到layout,

或者说一个元素“has layout” 的时候,我们的意思是指它的微软专有属性 hasLayout 被设为了 true。通过IE Developer Toolbar 可以查看 IE 下 HTML元素是否拥有haslayout,在 IE Developer Toolbar 下,拥有 haslayout的元素,通常显示为“haslayout = -1”。

  值得注意的是,css下是没有haslayout这一个属性的,只能通过把某些属性设置特定值来使ie下的hasLayout属性触发。这个属性在ie8及以后版本中被抛弃。

激活“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-heightmax-height(除none)、min-widthmax-width(除none)设置任意值——针对ie7。

  • overflowoverflow-xoverflow-yvisible外任意值——针对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")

注意:当用inline-block激活了haslayout 属性时,就算在一条独立的规则中覆盖这个属性为blockinline,haslayout 这个标志位也不会被重置为 false。把 min-width, min-height 设为它们的默认值"0"仍然会赋予 hasLayout,但是 IE 7 却可以接受一个不合法的属性auto来重置 hasLayout。


块级格式化上下文BFC

BFC是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在布局中的作用

  • 解决两元素margin重叠的问题

    要想两个相邻的元素不发生垂直方向上的margin重叠,需要将他们两定义在不同的BFC中。解决方法即在其中一个元素外包裹一层元素,再对那层包裹的元素进行BFC触发。(这里可以加入上述的css属性。)

  • 解决由于浮动造成的重叠问题

    一般情况下,浮动元素会脱离文档流,即不占位置。它的兄弟元素会与它在左上角重叠。但是如果两个相邻元素都设置了浮动,那么意味着它们都是以BFC的规则渲染,根据上述第四条规则,BFC区域不会相互重叠,所以便能理解为什么设置浮动后元素能独占空间了。

  • 解决容器由于拥有浮动元素造成高度塌陷的问题

    在普通容器中,如果里面有浮动元素,在不设置高度的情况下,容器是不能被撑起来的,这时候通过设置overflow:hidden把其变为BFC,那么就可以包含浮动元素了。


BFC的说明到此就告一段落了,现在回到最初讨论的haslayout的问题。ie7及以下ie版本不支持BFC的,但有私有属性haslayout,于是我们可以通过触发元素的haslayout来达成bfc的相似效果。

IE下因为haslayout导致的bug

1、浮动元素与普通元素之间产生3px bug

//代码如下
<style>
.test {width: 800px;margin: 10px auto;border: 1px solid brown;height: 30px;}
.float {float: left;background: saddlebrown;color: #fff;}
}
</style>

<div class="test">
    <div class="float">我是浮动元素</div>我是后面的文字,用来测试3px的bug
</div>

正常情况:
clipboard.png

ie6下:
clipboard.png

解决方式:加一个ie6的hack:*margin-right:-3px;

不只是文字,ie6的浮动元素也会和内联元素产生3px的margin值。

正常情况:
clipboard.png

ie6,7下:
clipboard.png

解决方式同上。

2、块级元素与浮动元素不会重叠

   <style>
    .test {width: 800px;margin: 10px auto;border: 1px solid brown;height: 30px;}
    .float {float: left;background: saddlebrown;color: #fff;}
    }
   </style>

    <div class="test">
        <div class="float">我是浮动元素</div>
        <div style="background: #0079F5;height: 30px;">我是浮动元素后面的块级元素</div>
    </div>

正常情况:
clipboard.png

ie6,7:
clipboard.png

可以明显地发现ie6,7下块级元素跟浮动元素不能重叠。为什么会发生这种情况呢?是因为我在块级元素上设置了高度。激活了ie下的haslayout属性。于是ie6把它以BFC类似的方式进行渲染。

解决方式:在块级元素外再包裹一层DIV。并且把内部DIVbackground的属性写在外层DIV上。

3、浮动闭合元素
这个问题其实很多人会遇到,上文也提到过,只是可能叫法不同。

<style>
.test {width: 800px;margin: 10px auto;border: 1px solid brown;}
.float {float: left;background: saddlebrown;color: #fff;}
</style>

<div class="test">
    <div class="float">我浮动啦!</div>
</div>

正常情况:
clipboard.png
注意那一条横线是.test的border,因为浮动元素脱离了文档流,故.test不能被撑起来。

ie6,7
clipboard.png

ie似乎妥妥的。其实很多情况下,我们想要的是ie这种效果。在ie中,一个浮动元素总是隶属于包含它的容器。是因为.test设置了宽度,激活了haslayout属性。而在非ie浏览器中,我们想要获得这种效果一般是在父盒子上加一个:after的伪对象来清除浮动,或者设置overflow:hidden来触发BFC。

插入提一下闭合浮动的普遍做法:
为需要闭合浮动的父元素加入clearfix的类。

.clearfix::after {content:"";display:block;height:0;clear:both;}
.clearfix {zoom: 1;} //兼容ie6,7

4、ie下margin不塌陷

<style>
.test {width: 800px;margin: 10px auto;}
.float {float: left;background: saddlebrown;color: #fff;}
</style>

<div class="test">
    <div class="float">我浮动啦!</div>
    <div style="margin-top:30px;">测试margin-top在ie下是否塌陷</div>
</div>

正常情况:
clipboard.png

ie6,7
clipboard.png

float是浮动元素,她脱离了文档流,所以第二个DIV的margin-top相对的是其上级.test作用的。但我们只是对第二个DIV设置margin-top。结果在chrome下,怎么连float也“产生了margin-top”呢。对比ie和chrome下的效果,是不是觉得IE下的 解析会比较合理呢?

但是。别忘了影响margin-top/bottom的一个重要规则——margin塌陷(margin collapsing)。

提到了margin塌陷,我们来看看margin垂直方向上塌陷的一些条件: 

  • 水平margin不会合并。

  • 两个上下渲染相邻(不一定是兄弟节点)的块状元素在正常页面流情况下会发生 margin 合并。

  • 浮动元素不会和任何元素(包括子孙节点)发生 margin 合并。

  • overflow!=visible的元素不和任何元素发生margin合并。

  • 绝对定位的元素不和任何元素发生margin合并。

  • inline-block 的元素不和任何元素发生margin合并。

  • 设置 clear 属性的元素不和任何元素发生margin合并。

  • 根元素不和任何元素发生margin合并。

  • 父节点和第一个子节点发生margin-top合并。

  • 如果最后一个子节点没有border以及padding,则和其父节点发生margin-bottom合并。

注意低版本IE下特别是hasLayout对于margin合并也有影响,从而也造成了包含的绝对定位元素的位置差异。

在现代浏览器下.test块的高度并没有被子元素第二个DIV的margin-top撑开。反而自身拥有了30px的margin-top
而浮动块尽管脱离了文档流,但还是受其父级限制的(这跟absolute定位的元素层受限于其定义为relative的父级一样)。所以float还是包含在test之中,这样在chrome下看起来浮动块也拥有margin-top,而事实上是因为test高度不撑开的结果。

这么说,chrome并没有错咯,那么IE下又是怎么避开margin塌陷的呢?问题就出在浮动上面,在ie下,元素浮动将触发其haslayout。就是这个原因,使得在ie下意外(意外?)的就避开了margin塌陷。

但是奇怪的是,如果给.test加上border,chrome的渲染情况就跟ie一样了,float没有拥有与第二个DIV相同的margin-top值。而是紧贴.test的顶部。如下图:

chrome:
clipboard.png

ie6,7:
clipboard.png

这个问题的原因还需要研究一下。。。。如果有知道原因的大神麻烦给留个言,,感激!

5、ie下margin-left/right失效

<style>
.test {margin: 10px auto;border:1px solid darkred;}
</style>

<div class="test">
    <div style="height:30px;margin:0 20px;border-bottom: 3px solid sandybrown;">测试失效的margin-left</div>
</div>

正常情况:
clipboard.png

ie6:
clipboard.png

根据上图可以发现,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

原文链接:
http://www.cnblogs.com/Remix/articles/4777257.html

阅读 3.3k

Nodes of Front-end
前端妹砸的笔记本。。

少年(゚∀゚ )有兴趣来鹅厂吗?欢迎投简历至yvonnexchen@tencent.com

1.2k 声望
51 粉丝
0 条评论

少年(゚∀゚ )有兴趣来鹅厂吗?欢迎投简历至yvonnexchen@tencent.com

1.2k 声望
51 粉丝
宣传栏