IE的hasLayout 介绍

IE的haslayout是个麻烦的东西,这个并不是w3c的标准属性,而是IE特有的。所以,对于解决IE特有的一些css诡异现象比较有效。

非原创来源网络,原文地址:http://www.satzansatz.de/cssd/onhavinglayout.html

hasLayout概述

Internet Explorer 中有很多奇怪的渲染问题可以通过赋予其“layout”得到解决。John Gallant 和 Holly Bergevin 把这些问题归类为”尺寸臭虫(dimensional bugs)”,意思是这些臭虫可以通过赋予相应元素某个宽度或高度解决。

“Layout”是一个 Internet Explorer for Windows的私有概念,它决定了一个元素如何显示以及约束其包含的内容、如何与其他元素交互和建立联系、如何响应和传递应用程序事件、用户事件等。这 种渲染特性可以通过某些 CSS 属性被不可逆转地触发。而有些 HTML 元素则默认就具有”layout”。

微软的开发者们认为元素都应该可以拥有一个”属性(property)”,于是他们便使用了 hasLayout,这种渲染特性生效时也就是将 hasLayout 设成了 true 之时。了解hasLayout将对IE的臭虫会有更多深入的体会甚至解决方案。
拥有layout的定义

一个元素”得到 layout”,或者说一个元素”拥有 layout” 的时候,是指它的微软专有属性 hasLayout 为此被设为了 true 。一个”layout元素”可以是一个默认就拥有 layout 的元素或者是一个通过设置某些 CSS 属性得到 layout 的元素。

而”无layout元素”,是指 hasLayout 未被触发的元素,比如一个未设定宽高尺寸的干净 div 元素就可以做为一个”无layout祖先”。

给一个默认没有 layout 的元素赋予 layout 的方法包括设置可触发 hasLayout = true 的 CSS 属性。参考默认 layout 元素以及这些属性列表。没有办法设置 hasLayout = false , 除非把一开始那些触发 hasLayout = true 的 CSS 属性去除或重置。
拥有layout的各种问题

Layout 在显示盒模型时有着不同寻常而且难以预料的效果,而且有时甚至会牵连到他们的子元素。

一个元素是否具有”layout”可能会引发如下的一些问题(包括但不限于):

* IE 很多常见的浮动bug。
* 元素本身对一些基本属性的异常处理问题。
* 容器和其子孙之间的空白边重叠问题。
* 使用列表时遇到的诸多问题。
* 背景图像的定位偏差问题。
* 使用脚本时遇到的浏览器之间处理不一致的问题。

Layout 的由来

不同于标准属性,也不像某些浏览器的私有 CSS 属性,layout 无法通过某一个 CSS 声明直接设定 。也就是说没有”layout属性”这么一个东西,元素要么本身自动拥有 layout,要么借助一些 CSS 声明悄悄地获得 layout。

下列元素应该是默认具有 layout 的:

* <html>, <body>
* <table>, <tr>, <th>, <td>
* <img>
* <hr>
* <input>, <button>, <select>, <textarea>, <fieldset>, <legend>
* <iframe>, <embed>, <object>, <applet>
* <marquee>

下列 CSS 属性和取值将会让一个元素获得 layout:

* position: absolute
绝对定位元素的包含区块(containing block)就会经常在这一方面出问题。
* float: left|right
由于 layout 元素的特性,浮动模型会有很多怪异的表现。
* display: inline-block
当一个内联级别的元素需要 layout 的时候往往就要用到它,这也可能也是这个 CSS 属性的唯一效果–让某个元素拥有 layout。”inline-block行为”在IE中是可以实现的,但是需要注意的是: IE/Win: inline-block and hasLayout 。
* width: 除 “auto” 外的任意值
很多人遇到 layout 相关问题发生时,一般都会先尝试用这个来修复。
* height: 除 “auto” 外的任意值
height: 1% 就在 Holly Hack 中用到。
* zoom: 除 “normal” 外的任意值
IE专有属性。不过 zoom: 1 可以临时用做调试。
* writing-mode: tb-rl
MS专有属性。
* overflow: hidden|scroll|auto
在 IE7 中,overflow 也变成了一个 layout 触发器,这个属性在之前版本 IE 中没有触发 layout 的功能。
* overflow-x|-y: hidden|scroll|auto
overflow-x 和 overflow-y 是 CSS3 盒模型中的属性,尚未得到浏览器的广泛支持。他们在之前版本IE中没有触发 layout 的功能。
* 另外 IE7 的荧幕上又新添了几个 haslayout 的演员,如果只从 hasLayout 这个方面考虑,min/max 和 width/height 的表现类似,position 的 fixed 和 absolute 也是一模一样。
* position: fixed
* min-width: 任意值
就算设为0也可以让该元素获得 layout。
* max-width: 除 “none” 之外的任意值
* min-height: 任意值。即使设为0也可以让该元素的 haslayout=true
* max-height: 除 “none” 之外的任意值

有关内联级别元素

对于内联元素(可以是默认即为内联的比如 span 元素,也可以是 display: inline 的元素)

* width 和 height 只在 IE5.x 下和 IE6 或更新版本的 quirks 模式下触发 hasLayout 。而对于 IE6,如果浏览器运行于标准兼容模式下,内联元素会忽略 width 或 height 属性,所以设置 width 或 height 不能在此种情况下令该元素具有 layout。
* zoom 总是可以触发 hasLayout,但是在 IE5.0 中不支持。

具有”layout” 的元素如果同时也 display: inline ,那么它的行为就和标准中所说的 inline-block 很类似了:在段落中和普通文字一样在水平方向和连续排列,受 vertical-align 影响,并且大小可以根据内容自适应调整。这也可以解释为什么单单在 IE/Win 中内联元素可以包含块级元素而少出问题,因为在别的浏览器中 display: inline 就是内联,不像 IE/Win 一旦内联元素拥有 layout 还会变成 inline-block。
重置 hasLayout

在另一条规则中重设以下属性为默认值将重置(或撤销)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 = true 时,就算在一条独立的规则中覆盖这个属性为”block”或”inline”,haslayout 这个标志位也不会被重置为 false。

把 mid-width, mid-height 设为它们的默认值”0″仍然会赋予 hasLayout,但是 IE 7 却可以接受一个不合法的属性”auto”来重置 hasLayout。

IE6/7 inline-block实现及分析

ie6,ie7的haslayout属性是个让人头疼的问题。在做导航条的时候,一般会用到ul li结构,大多数时候我们是把li设置为浮动,让其并排显示在同一行。还有一种方法就是设置li为display:inline;这样可以达到同样的效果,但是问题是inline元素的特性:默认无法设置宽度,高度,以及上下margin,(关于padding,情况有点特殊,在ie6,7中 inline元素是无法设置上下padding的,但是在标准浏览器里面是可以设置上下padding的)。

鉴于inline元素的这种特性,如果我们不浮动并且想让li显示在一行,而且可以设置高度,宽度以及上下margin,上下padding等属性,应该怎么办呢?你一定会想到一个属性display:inline-block;对!”inline- block”就是干这个事的,让一个元素既不换行又具有block元素的特性。不过有点小问题,让我们看代码:

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>display-block</title>
<style>
ul {background:#ccc;padding:0;margin:0;list-style:none;}
li {display:inline-block;
width:80px;
height:20px;
margin:10px;
padding:10px;
text-align:center;
background:#cfc;}
</style>
</head>
<body>
<ul>
<li>测试</li>
<li>测试</li>
<li>测试</li>
<li>测试</li>
</ul>
</body>
</html>

上面的例子,在firefox,opera等标准浏览器里面已经达到效果了,但是在ie6和ie7下却发现每个li元素仍然独占一行,(关于margin合并的问题日后再说)。这是因为ie6和ie7并不完全支持inline-block这个属性值。通过上面的例子会发现加不加display:inline-block;对于它们完全没有任何影响。

那么让我们来想办法解决这个问题,这就涉及到ie6,7中的haslayout属性了。ie6,7中的inline元素有个特殊的情况,就是触发了ie的hasLayout属性以后就拥有了layout。此时inline元素的表现和标准浏览器里面的inline-block元素基本相同。看下面这个例子,我们用ie的私有属性zoom来触发hasLayout,然后看看inline元素的表现。

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>display-block</title>
<style>
span {width:200px;height:50px;margin:10px;padding:20px;background:#ccc;zoom:1;}
</style>
</head>
<body>
<span>span</span>
</body>
</html>

可以看到在ie6,7中inline元素span已经表现得和一个display:inline-block元素一摸一样了,但是在标准浏览器中span仍然是行内元素(宽高以及上下margin都无效)。

如果声明了不正确DTD,导致ie6在quirks 模式下解析,那么ie6会自动触发inline元素的haslayout,不过这里只讨论正常情况下的解析,所以加了个zoom:1来触发haslayout;zoom的值设置为除了auto外的任何值都会触发haslayout,之所以经常用zoom:1;是因为zoom这个属性本身是ie的缩放属性,设置为其他值会导致元素在ie下变形,设置为1既是保持原形不缩放。

了解了上面的情况,我们就可以来解决之前那个问题了。可以改原先的css代码如下:

li {display:inline-block;    /* firefox等标准浏览器识别*/
*display:inline;      /* 只有ie6和ie7识别*/
zoom:1;               /* 触发ie6和ie7下的haslayout */
width:80px;
height:20px;
margin:10px;
padding:10px;
text-align:center;
background:#cfc;
}
让标准浏览器识别display:inline-block;让ie6,7识别display:inline;来覆盖上面的display:inline-block;(我为什么要说”覆盖”?)。然后通过zoom:1;来触发haslayout让inline元素在ie中表现得和inline-block元素一样。

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>display-block</title>
<style>
ul {background:#ccc;padding:0;margin:0;list-style:none;}
li {display:inline-block;
*display:inline;
zoom:1;
width:80px;
height:20px;
margin:10px;
padding:10px;
text-align:center;
background:#cfc;
}
</style>
</head>
<body>
<ul>
<li>测试</li>
<li>测试</li>
<li>测试</li>
<li>测试</li>
</ul>
</body>
</html>

可以看到,现在在各浏览器里面的显示已经一致了。li元素都显示在同一行。

不过我上面问了:为什么要说覆盖?

假如我们把上面的css代码中的

display:inline-block;
*display:inline;

两句调换一下顺序会怎样?你会发现display:inline-block;覆盖了*display:inline;导致在ie6,7中原先的样式又失效了,这说明什么?说明ie6和ie7是认识display:inline-block的,所以我在前面说”不完全支持”而没有说”完全不支持”,嘿嘿,我可没有打自己嘴巴。之所以说”不完全支持”是因为还是有一点作用,虽然display:inline-block对ie6,7中的元素表现没有任何直接影响,但是它会触发inline元素的haslayout。只有这一个作用,让我们再回到前面的例子:

span {width:200px;height:50px;margin:10px;padding:20px;background:#ccc;
display:inline-block;}

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
<title>display-block</title>
<style>
span {width:200px;height:50px;margin:10px;padding:20px;background:#ccc;display:inline-block;}
</style>
</head>
<body>
<span>span</span>
</body>
</html>

可以看到,我们把zoom:1换成了display:inline-block以后,在ie6,7中span仍然和firefox等标准浏览器下一样,具有了设置的宽高和上下margin,上下padding。它表现得和display:inline-block同学一样好,不过这并不是因为display:inline-block这条声明直接生效了,而是因为display:inline-block触发了ie的haslayout属性,使得inline元素具有了inline-block的表现。

hasLayout——IE中css bug的罪魁祸首

什么是hasLayout?

要想更好的理解css,尤其是 IE 下对 css 的渲染,haslayout 是一个非常有必要彻底弄清楚的概念。大多IE下的显示错误,就是源于 haslayout。
haslayout 是Windows Internet Explorer渲染引擎的一个内部组成部分。在InternetExplorer中,一个元素要么自己对自身的内容进行计算大小和组织,要么依赖于父元素来计算尺寸和组织内容。为了调节这两个不同的概念,渲染引擎采用了 hasLayout 的属性,属性值可以为true或false。当一个元素的 hasLayout属性值为true时,我们说这个元素有一个布局(layout)。
通过 IE Developer Toolbar 可以查看 IE 下 HTML元素是否拥有haslayout,在 IE Developer Toolbar 下,拥有 haslayout的元素,通常显示为“haslayout = -1”。

什么情况下hasLayout不会出现?

一般情况都不会出现,当然除了下面罗列的默认具有hasLayout的元素和使用特定样式触发以外;
它会带来各种诡异表现,当你发现IE7-出现了一些不可思议的问题,首先要检查的就是是否是hasLayout在捣鬼;
hasLayout只出现在IE7及更早版本中,IE8不存在hasLayout解析模式,我们只讨论指定doctype的情况。

hasLayout的触发条件:

* position: absolute(IE5+)
* float: left|right(IE5+)
* display: inline-block(IE5+)
* width: “auto”以外的任何值(IE5+; 对inline元素无效)
* height: “auto”以外的任何值(IE5+; 对inline元素无效)
* zoom: “normal”以外的任何值(IE5.5+; IE私有属性)
* writing-mode: tb-rl(IE5+; IE私有属性)
* overflow: hidden|scroll|auto(IE7; 此属性在IE6及更早版本中不能应用在未触发hasLayout的元素上)
* overflow-x|-y: hidden|scroll|auto(IE7; 此属性在IE6及更早版本中不触发hasLayout; 此属性在CSS3中才获支持)
* min-width: 任何值(IE7; 即使是0)
* max-width: “none”以外的任何值(IE7)
* min-height: 任何值(IE7)
* max-height: “none”以外的任何值 (IE7)
* position: fixed(IE7)

清除或重置hasLayout:

* position: static(IE5+)
* float: none(IE5+)
* display: “inline-block”以外的任何值(IE5+)
* width|height: “auto”(IE5+; 对inline元素无效)
* zoom: “normal”(IE5.5+; IE私有属性)
* writing-mode: 从’tb-rl’到’lr-tb'(IE5+; IE私有属性)
* max-width|max-height: “none”(IE7)
* overflow: visible(IE7)

haslayout 问题的调试与解决

当网页在ff中表现正常,而在 IE7 或更早版本中有异常表现时,可以尝试激发 haslayout 来看看是不是问题所在。常用的方法是给某元素 css 设定zoom:1。使用 zoom:1 是因为大多数情况下,它能在不影响现有环境的条件下激发元素的 haslayout。而一旦问题消失,那基本上就可以判断是haslayout 的原因。然后就可以通过设定相应的 css 属性来对这个问题进行修正了。建议首先要考虑的是设定元素的width/height 属性,其次再考虑其他属性。
对于IE7 ,最好的办法是设置最小高度属性为0;这个技术是无害的,因为0本来就是这个属性的初始值。而且没有必要对其他浏览器隐藏这个属性。
对 IE6 及更早版本来说,常用的方法被称为霍莉破解(Holly hack),即设定这个元素的高度为 1%(height:1%;)。需要注意的是,当这个元素的 overflow 属性被设置为 visible 时,这个方法就失效了。或者使用 IE的条件注释。