譯者注:一篇很好的文章,很久以前在blog上就推薦過,這兩天斷斷續(xù)續(xù)花了點(diǎn)時(shí)間翻譯了一下,推薦讀讀。英文原文在此。
文中所有的 layout 這個(gè)單詞都未作翻譯,一來本身這個(gè)單詞意思就比較多,翻成啥都覺得別扭,二來它也是專有的屬性,所以就意會(huì)一下吧。水平有限,很多地方都是模模糊糊地意譯,發(fā)現(xiàn)錯(cuò)誤歡迎留言指出。
引用一段來自Dean Edwards的評(píng)價(jià):
I recommend that every CSS designer and DOM scripter read this. Understanding “l(fā)ayout” gives a huge insight into lots of other IE bugs and idiosyncrasies.
(Dean Edwards)
介紹
Internet Explorer 中有很多奇怪的渲染問題可以通過賦予其“l(fā)ayout”得到解決。John Gallant 和 Holly Bergevin 把這些問題歸類為“尺寸bug(dimensional bugs)”,意思是這些 bug 可以通過賦予相應(yīng)元素某個(gè)寬度或高度解決。這便引出關(guān)于“l(fā)ayout”的一個(gè)問題:為什么它會(huì)改變?cè)氐匿秩咎匦?,為什么它?huì)影響到元素之間的關(guān)系?這個(gè)問題問得很好,但卻很難回答。在這篇文章中,我們專注于這個(gè)復(fù)雜問題會(huì)有那些方面的表現(xiàn),某一方面的具體討論和范例請(qǐng)參考文中給出的相關(guān)鏈接。
hasLayout — 定義
“Layout”是一個(gè) IE/Win 的私有概念,它決定了一個(gè)元素如何顯示以及約束其包含的內(nèi)容、如何與其他元素交互和建立聯(lián)系、如何響應(yīng)和傳遞應(yīng)用程序事件/用戶事件等,這有點(diǎn)類似于一個(gè)窗體的概念。
微軟的開發(fā)者們認(rèn)為盒狀元素(box-type elements)應(yīng)該具有一個(gè)“屬性(property)”(這是面向?qū)ο缶幊讨械囊粋€(gè)概念),于是他們便使用了 layout
, 也就是 hasLayout
。
hasLayout
其實(shí)既不是一個(gè)屬性更不是一個(gè)行為,而是 IE 這個(gè)渲染引擎代代繼承一貫擁有的一個(gè)渲染概念,在這個(gè)概念下渲染的元素將具有一種特性。
實(shí)際上這種渲染特性在有些 HTML 元素中與身俱來,而在另外一些元素中也可以通過一些 CSS 屬性將其觸發(fā)為 true
,且一旦觸發(fā)將不可逆轉(zhuǎn)。
術(shù)語
當(dāng)我們說一個(gè)元素“擁有l(wèi)ayout”或“得到layout”,或者說一個(gè)元素“has layout” 的時(shí)候,我們的意思是指它的微軟專有屬性 hasLayout
被設(shè)為了 true
。一個(gè)“l(fā)ayout元素”可以是一個(gè)默認(rèn)就擁有 layout 的元素或者是一個(gè)通過設(shè)置某些 CSS 屬性得到 layout 的元素。
而“無layout元素”,是指 hasLayout 未被觸發(fā)的元素,比如一個(gè)未設(shè)定寬高尺寸的干凈 div 元素就可以做為一個(gè) “無layout祖先”。
給一個(gè)默認(rèn)沒有 layout 的元素賦予 layout 的方法包括設(shè)置可觸發(fā) hasLayout = true
的 CSS 屬性。參考默認(rèn) layout 元素以及這些屬性列表。沒有辦法設(shè)置 hasLayout = false
, 除非把一開始那些觸發(fā) hasLayout = true
的 CSS 屬性去除。
問題種種
hasLayout
的問題不管新手還是老手,不管設(shè)計(jì)師或者程序員可能都遇到過。具有 layout 的元素通常有著不同尋常而且難以預(yù)料的的顯示效果,而且有時(shí)甚至?xí)窟B到他們的孩子元素。
一個(gè)元素是否具有“l(fā)ayout”可能會(huì)引發(fā)如下的一些問題:
- IE 很多常見的浮動(dòng) bug 。
- 元素本身對(duì)一些基本屬性的異常處理問題。
- 容器和其子孫之間的邊距重疊(margin collapsing)問題。
- 使用列表時(shí)遇到的諸多問題。
- 背景圖像的定位偏差問題。
- 使用腳本時(shí)遇到的瀏覽器之間處理不一致的問題。
上面的列表只是列出一個(gè)大概,也不完善。下面的文章將盡可能詳細(xì)徹底的描述有無“l(fā)ayout”所帶來的各種問題。
Layout 從何而來
不同于標(biāo)準(zhǔn)屬性,也不像某些瀏覽器的私有 CSS 屬性,layout 無法通過某一個(gè) CSS 聲明直接設(shè)定 。也就是說沒有“l(fā)ayout屬性”這么一個(gè)東西,元素要么本身自動(dòng)擁有 layout,要么借助一些 CSS 聲明悄悄地獲得 layout。
默認(rèn)layout元素
下列元素應(yīng)該是默認(rèn)具有 layout 的:
html>, body>
table>, tr>, th>, td>
img>
hr>
input>, select>, textarea>, button>
iframe>, embed>, object>, applet>
marquee>
屬性
下列 CSS 屬性和取值將會(huì)讓一個(gè)元素獲得 layout:
position: absolute
- 絕對(duì)定位元素的包含區(qū)塊(containing block)就會(huì)經(jīng)常在這一方面出問題。
float: left|right
- 由于 layout 元素的特性,浮動(dòng)模型會(huì)有很多怪異的表現(xiàn)。
display: inline-block
- 當(dāng)一個(gè)內(nèi)聯(lián)級(jí)別的元素需要 layout 的時(shí)候往往就要用到它,這也可能也是這個(gè) CSS 屬性的唯一效果——讓某個(gè)元素?fù)碛?layout?!癷nline-block行為”在IE中是可以實(shí)現(xiàn)的,但是非常與眾不同: IE/Win: inline-block and hasLayout 。
width: 任意值
- 很多人遇到 layout 相關(guān)問題發(fā)生時(shí),一般都會(huì)先嘗試用這個(gè)來修復(fù)。
height: 任意值
- height: 1% 就在 Holly Hack 中用到。
zoom: 任意值
(MSDN)
- MS專有屬性,無法通過校驗(yàn)。 不過
zoom: 1
可以臨時(shí)用做調(diào)試。
writing-mode: tb-rl
(MSDN)
- MS專有屬性,無法通過校驗(yàn)。
在 IE7 中,overflow 也變成了一個(gè) layout 觸發(fā)器:
overflow: hidden|scroll|auto
- 這個(gè)屬性在之前版本 IE 中沒有觸發(fā) layout 的功能。
overflow-x|-y: hidden|scroll|auto
- overflow-x 和 overflow-y 是 CSS3 盒模型中的屬性,尚未得到瀏覽器的廣泛支持。他們?cè)谥鞍姹綢E中沒有觸發(fā) layout 的功能。
另外 IE7 的熒幕上又新添了幾個(gè) haslayout 的演員,如果只從 hasLayout 這個(gè)方面考慮,min/max 和 width/height 的表現(xiàn)類似,position 的 fixed 和 absolute 也是一模一樣。
position: fixed
- ./.
min-width: 任意值
- 就算設(shè)為0也可以讓該元素獲得 layout。
max-width: 除 none 之外的任意值
- ./.
min-height: 任意值
- 即使設(shè)為0也可以讓該元素的 haslayout=true
max-height: 除 none 之外的任意值
- ./.
以上結(jié)論借助 IE Developer Toobar 以及預(yù)先測(cè)試得出。
有關(guān)內(nèi)聯(lián)級(jí)別元素
對(duì)于內(nèi)聯(lián)元素(可以是默認(rèn)即為內(nèi)聯(lián)的比如 span
元素,也可以是 display: inline
的元素)
width
和 height
只在 IE5.x 下和 IE6 的 quirks 模式下觸發(fā) hasLayout
。因?yàn)樵?IE6 中,如果瀏覽器運(yùn)行于標(biāo)準(zhǔn)兼容模式下,內(nèi)聯(lián)元素會(huì)忽略 width 或 height 屬性,所以設(shè)置 width 或 height 不能在此種情況下令該元素具有 layout。
zoom
總是可以觸發(fā) hasLayout
,但是在 IE5.0 中不支持。
具有“l(fā)ayout” 的元素如果同時(shí)也 display: inline
,那么它的行為就和標(biāo)準(zhǔn)中所說的 inline-block 很類似了:在段落中和普通文字一樣在水平方向和連續(xù)排列,受 vertical-align 影響,并且大小可以根據(jù)內(nèi)容自適應(yīng)調(diào)整。這也可以解釋為什么單單在 IE/Win 中內(nèi)聯(lián)元素可以包含塊級(jí)元素而少出問題,因?yàn)樵趧e的瀏覽器中 display: inline
就是內(nèi)聯(lián),不像 IE/Win 一旦內(nèi)聯(lián)元素?fù)碛?layout 還會(huì)變成 inline-block。
腳本屬性 hasLayout
我們這里稱 hasLayout 為“腳本屬性”是為了和我們熟知的 CSS 屬性相區(qū)別。
注意一旦一個(gè)元素?fù)碛辛?layout,就沒有辦法再將其設(shè)成 hasLayout = False
了。
hasLayout-property 可以用來檢測(cè)一個(gè)元素是否擁有 layout:舉個(gè)例子,如果它的 id
是“eid”,那么只要在 IE5.5+ 的地址欄里輸入 javascript: alert(eid.currentStyle.hasLayout)
即可檢測(cè)它的狀態(tài)。
IE的 Developer Toolbar 可以實(shí)時(shí)檢查一個(gè)元素的當(dāng)前樣式;如果 hasLayout
是 true ,那么它的值顯示為 “-1”。 我們可以通過實(shí)時(shí)修改一個(gè)元素的屬性將“zoom(css)”設(shè)置為“1”來觸發(fā) hasLayout
以便調(diào)試。
另外一個(gè)需要注意的是“l(fā)ayout”會(huì)影響腳本編程。如果一個(gè)元素沒有“l(fā)ayout”,那么clientWidth
/clientHeight
總是返回0。這會(huì)讓一些腳本新手感到困惑,而且這和 Mozilla 瀏覽器的處理方式也不一樣。不過我們可以利用這一點(diǎn)在 IE5.0 中檢測(cè)“l(fā)ayout”:如果 clientWidth
是零那么這個(gè)元素就沒有 layout。
CSS hacks
下面用于觸發(fā) haslayout
的 hack 已經(jīng)經(jīng)過 IE6 及以下版本測(cè)試。今后版本的IE有可能會(huì)對(duì)此做不同處理。如果新版本瀏覽器發(fā)布我們會(huì)重新整理這部分內(nèi)容。
John Gallant 和 Holly Bergevin 在2003年發(fā)布的 Holly hack :
/* \*/
* html .gainlayout { height: 1%; }
/* */
- 可以讓 IE5+ 的任意元素獲得 layout,除了標(biāo)準(zhǔn)兼容模式 IE6 中的內(nèi)聯(lián)元素。
- 一般都很有效,除了在某些極少情況下,需要用 height:0 或者 1px 更好一些。
- 和
overflow: hidden
不相容,除非在 IE6 的標(biāo)注兼容模式下(因?yàn)檫@時(shí)如果父元素沒有定高,那么height: 1%
會(huì)被變回 height: auto
)。
或者我們可以用 underscore hack:
.gainlayout { _height: 0; }
另外,更具有向后兼容性的方法是使用 條件注釋(conditional comments):
!--[if lte IE 6]>
style>
.gainlayout { height: 1px; }
/style>
![endif]-->
在條件注釋中鏈接一個(gè)專門對(duì) IE/Win 做修正的外部樣式表文件,也不失為一個(gè)安全有效的好方法:
link rel="stylesheet" href="allbrowsers.css" type="text/css" />
!--[if lte IE 6]>
link rel="stylesheet" href="iefix.css" type="text/css" />
![endif]-->
我們更傾向于使用 height: 0
和 1px
—— 并主張始終使用 height
除非它和別的什么東西沖突 (overflow: hidden
)。對(duì)于取值,我們則傾向于避免 1%
,因?yàn)樗赡軙?huì)(雖然很少)引起一些問題。
一個(gè)需要注意的情況是如果我們希望一個(gè)元素保持內(nèi)聯(lián),那么就不能使用 height
了,這時(shí)可以用 display: inline-block
。我們只在早期調(diào)試階段用 zoom: 1
來避免一些渲染錯(cuò)誤。
我們?cè)催^一些把 Holly hack 真的當(dāng)作 holy(神圣的) hack 盲目使用的情況,比如對(duì)浮動(dòng)元素使用或者對(duì)已經(jīng)具有特定寬度的元素也使用這個(gè) hack。要記住這個(gè) hack 的目的不是要給某個(gè)元素加一個(gè)高度,而只是要觸發(fā) hasLayout = True
而已。
不要給所有元素設(shè)置 layout:* {_height: 1px;}
。所謂過猶不及,獲得 layout 不等于獲得靈丹妙藥,它只是用來改變渲染模式。
Hack整理
但是瀏覽器總是會(huì)變的,我們需要面對(duì)很多問題,比如一些依賴 IE6 的 bug 所做的 hack 會(huì)在 IE7 或更高版本的新瀏覽器中因 bug 修復(fù)而失效(甚至有害)的問題;比如新版本瀏覽器中類似的布局 bug 依然存在但用于 hack 的過濾器比如 * html
卻不能正常工作的問題。這種情況下,MS專有屬性 zoom
就可以考慮使用了。
!--[if lt IE 7]>style>
/* IE 6 + IE5.5 + IE5.0 所用樣式*/
.gainlayout { height: 0; }
/style>![endif]-->
!--[if IE 7]>style>
.gainlayout { zoom: 1;}
/* 或者其他任何以后可能需要的東西 */
/style>![endif]-->
zoom: 1;
可以讓 IE5.5+ 的任何元素(包括內(nèi)聯(lián)元素)獲得 layout,但是在 IE5.0 中無效。
- 沒有其他附帶效果(內(nèi)聯(lián)元素會(huì)變成 inline-block,這個(gè)當(dāng)然)。
- 如果需要通過驗(yàn)證,應(yīng)該用條件注釋將
zoom
隱藏起來。
其實(shí)當(dāng)我們考慮到“向后兼容”時(shí)是很自相矛盾的,我們強(qiáng)烈建議頁面設(shè)計(jì)者回過頭看一下自己頁面中用的到的明顯的或是不明顯的“hacks”,并用條件注釋針對(duì)不同瀏覽器重新處理以保萬無一失。
關(guān)于IE Mac 的小問題
IE Mac 和 windows 下的 IE 是完全不同的兩個(gè)東西,它們各自擁有自己的渲染引擎,IE Mac 就全然不知“hasLayout”(或contenteditable)所謂何物。相比之下 IE Mac 的渲染引擎要更標(biāo)準(zhǔn)兼容一點(diǎn),比如 height
就是被當(dāng)作 height
處理,沒有別的效果。因此針對(duì)“hasLayout”的 hacks 和別的解決方法(特別是通過使用 height
或 width
屬性的)往往對(duì) IE Mac 來說是有害的,所以需要對(duì)其隱藏。更多的關(guān)于 IE Mac 相關(guān)的問題可以在 IE Mac, bugs and oddities pages 找到。
MSDN 文檔
MSDN 中涉及到 hasLayout 這個(gè) MS 屬性的地方寥寥無幾,而具體解釋 layout 和 IE 渲染模型之間關(guān)系的則少之又少。
在IE4的時(shí)候,除了未經(jīng)絕對(duì)定位也未指定寬高的內(nèi)聯(lián)元素,幾乎所有元素都有某種 layout(MSDN)。在這種早期的layout概念中,像 border, margin, padding
這些屬性被稱作“l(fā)ayout屬性”,它們是不能應(yīng)用到一個(gè)簡(jiǎn)單的內(nèi)聯(lián)元素上的。換句話說,“擁有l(wèi)ayout”就可以粗略理解成:“可以擁有這幾個(gè)屬性”。
MSDN 上仍然使用 layout 屬性這種說法, 只是含義變了,它們和擁有 layout 的元素已經(jīng)沒有什么關(guān)系了。在 IE5.5 中方才引入了 MS 的這個(gè)專有屬性 hasLayout
,也只是某種內(nèi)部的標(biāo)志位而已。
在 IE5.5 中,MSHTML Editing Platform(即可以通過設(shè)置body contenteditable=true>
來允許用戶實(shí)時(shí)編輯、拖動(dòng) layout 元素以及調(diào)整其尺寸等)的文檔中描述了三個(gè)和 layout 相關(guān)的重要特性:
如果一個(gè) layout 元素中有內(nèi)容,內(nèi)容的排版布局將由它的邊界矩形框決定。
擁有 layout 的意思基本上就是表示某元素是一個(gè)矩形。
從內(nèi)部來說,擁有 layout 意思就是一個(gè)元素將自己負(fù)責(zé)繪制其內(nèi)部?jī)?nèi)容。
(Editing Platform)
和 layout 自身相關(guān)的內(nèi)部工作機(jī)制直到2005年8月才有相應(yīng)文檔描述,當(dāng)時(shí)由于 The Web Standards Project 和微軟的特別工作小組的原因,Markus Mielke [MSFT] 打開了深入討論的大門:
一般來說,在 Internet Explorer 的 DHTML 引擎中,元素是不對(duì)自己的位置安排負(fù)責(zé)的。雖然一個(gè) div 或者一個(gè) p 元素都在源碼中有一個(gè)位置,在文檔流有一個(gè)位置,但是它們的內(nèi)容卻是由它們最近的一個(gè) layout 祖先(經(jīng)常是 body)控制安排的。這些元素依賴它們祖先的 layout 來為他們處理諸如決定大小尺寸和測(cè)量信息等諸多繁重的工作。
(HasLayout概述)
分析
我們的分析試圖解釋在已知案例下發(fā)生了什么事情,這種分析也應(yīng)該可以作為未知案例下的指導(dǎo)。但我們這種試圖利用種種測(cè)試案例投石探路的黑箱測(cè)試方法,是注定無法消除黑箱的神秘感的——我們無法回答“為什么”的問題。我們只能去嘗試了解整個(gè)“hasLayout”模式的工作框架,以及它會(huì)怎樣影響網(wǎng)頁文檔的渲染。因此,最終我們只能提供一些指導(dǎo)方針(而且只能是指導(dǎo)方針,而不是絕對(duì)的解決方案)。
我們認(rèn)為他們所指的是一個(gè)小窗體。一個(gè) layout 元素的內(nèi)部?jī)?nèi)容是完全獨(dú)立的,而且也無法影響其邊界外的任何內(nèi)容。
而 MS 屬性 layout 只是某種標(biāo)志位:一旦它被設(shè)定,這個(gè)元素就會(huì)擁有其特殊的 layout“特性”,這包括體現(xiàn)在其自身以及其非 layout 孩子身上的浮動(dòng)、清除浮動(dòng)、層疊、計(jì)數(shù)等等諸多方面的特殊性能。
這種獨(dú)立性也許正可以解釋為什么 layout 元素通常比較穩(wěn)定,而且它們可以讓某些 bug 消失。這種情況的代價(jià)有二,一是偏離了標(biāo)準(zhǔn),二是它沒有考慮到今后可能因此出現(xiàn)的 bug 和問題。
MS 的“頁面”模式,從符號(hào)學(xué)角度考慮,可以看做是由很多互不相關(guān)的小的區(qū)塊構(gòu)成,而 HTML 和 W3C 的模式則認(rèn)為“頁面”模式應(yīng)該是敘述完備的,故事性的相關(guān)信息區(qū)塊構(gòu)成的。
各種情況的詳細(xì)說明
清除浮動(dòng)和自動(dòng)擴(kuò)展適應(yīng)高度
浮動(dòng)元素會(huì)被 layou 元素自動(dòng)包含。這是很多新手經(jīng)常遇到的問題:在 IE 下完成的頁面到了標(biāo)準(zhǔn)兼容瀏覽器下所有未清除的浮動(dòng)元素都伸出了其包含容器之外。
- Containing Floats
- how to clear floats without structural markup
相反的情況:如果確實(shí)需要一個(gè)浮動(dòng)元素伸出其包含容器,也就是自動(dòng)包含不是想要的效果時(shí),該怎么辦?你很可能也會(huì)遇到這種頭疼的問題,下面的深入討論就是一個(gè)例子:
在IE中,一個(gè)浮動(dòng)元素總是“隸屬于”它的 layout 包含容器。而后面的元素會(huì)受這個(gè) layout 包含容器影響而不是這個(gè)浮動(dòng)元素影響。
這個(gè)特性和IE6的那個(gè)自動(dòng)擴(kuò)展以適應(yīng)內(nèi)部?jī)?nèi)容寬度的特性,都可以看成是受這個(gè)規(guī)則影響的:“由它的邊界矩形框決定”。
更糟的問題:clear
無法影響其 layout 包含容器之外的 float 元素。如果依賴這個(gè) bug 在 IE 中布局的頁面要轉(zhuǎn)到標(biāo)準(zhǔn)兼容瀏覽器中,只有全部重做。
更多相關(guān)信息查看本文 “和 CSS 規(guī)范類似的地方” 這一部分。
浮動(dòng)元素旁邊的元素
當(dāng)一個(gè)塊級(jí)元素緊跟在一個(gè)左浮動(dòng)元素之后時(shí),其中的文字內(nèi)容應(yīng)該沿著浮動(dòng)元素的右邊順序排列并會(huì)滑到浮動(dòng)元素下方。但是如果這個(gè)塊級(jí)元素有 layout,比如由于某種原因被設(shè)置了寬度,那么這個(gè) layout 元素就會(huì)表現(xiàn)為一個(gè)矩形,其中文字不會(huì)滑向浮動(dòng)元素下方。其寬度也被錯(cuò)誤計(jì)算——從浮動(dòng)元素的右邊開始算起了,所以如果給它設(shè)置 width: 100%
將會(huì)導(dǎo)致顯示時(shí)這個(gè) block 的寬度加上了浮動(dòng)元素的寬度而出現(xiàn)橫向滾動(dòng)條。這種表現(xiàn)就和規(guī)范中描述的相去甚遠(yuǎn)了。
與此類似,和浮動(dòng)元素相鄰的相對(duì)定位元素,它的位置偏移量應(yīng)該參照的是父元素的補(bǔ)白(padding)邊緣(例如,left: 0;
應(yīng)該將一個(gè)相對(duì)定位元素疊放于它前面的浮動(dòng)元素之上)。在IE中,偏移量 left: value;
是從浮動(dòng)元素的右邊距(margin)邊緣開始算起的,這會(huì)因浮動(dòng)元素所占的寬度變化導(dǎo)致水平方向的錯(cuò)位(一個(gè)解決方法是用 margin-left
代替,但是也要注意如使用百分值時(shí)會(huì)有一些怪異問題)。
根據(jù)規(guī)范所述,浮動(dòng)元素應(yīng)該與其后的盒子交織在一起。而對(duì)于沒有交叉的二維空間中的矩形而言這是無法實(shí)現(xiàn)的。
可以(再次)訪問下面這個(gè)頁面:
我們可以看到跟在一個(gè)浮動(dòng)元素后的 layout 元素不會(huì)顯示這個(gè)3px間隙的 bug,因?yàn)楦?dòng)元素外圍的3px硬邊無法影響一個(gè) layout 元素的內(nèi)部?jī)?nèi)容,所以這個(gè)硬邊將整個(gè) layout 元素右推了3px。好比一個(gè)防護(hù)罩,layout 可以保護(hù)其內(nèi)部?jī)?nèi)容不受影響,但是浮動(dòng)元素的力量卻將整個(gè)防護(hù)罩推了開來。
更多相關(guān)信息查看本文 “和 CSS 規(guī)范類似的地方” 這一部分
列表
無論是列表本身(ol, ul
) 還是單個(gè)的列表元素(li
),擁有 layout 后都會(huì)影響列表的表現(xiàn)。不同版本 IE 的表現(xiàn)又有不同。最明顯的效果就體現(xiàn)在列表符號(hào)上(如果你的列表自定義了列表符號(hào)則不會(huì)受這個(gè)問題影響)。這些符號(hào)很可能是通過某種內(nèi)部機(jī)制附到列表元素上的(通常是附著在它們外面)。不幸的是,由于是通過“內(nèi)部機(jī)制”添加的,我們無法訪問它們也無法修正它們的錯(cuò)誤表現(xiàn)。
最明顯的效果有:
- 列表獲得 layout 后,列表符號(hào)會(huì)消失或者被放置在不同的或者錯(cuò)誤的位置。
有時(shí)它們又可以通過改變列表元素的邊距而重新出現(xiàn)。這看起來似乎是以下事實(shí)導(dǎo)致的結(jié)果:layout 元素會(huì)試圖裁掉超出其邊界的內(nèi)部?jī)?nèi)容。
- 列表元素獲得 layout 之后,會(huì)有和上面一樣的問題出現(xiàn),更多參考 (extra vertical space between list items)
進(jìn)一步又有一個(gè)問題就是(在有序列表中)任何具有 layout 的列表元素似乎都有自己獨(dú)立的計(jì)數(shù)器。比如我們有一個(gè)含五個(gè)列表元素的有序列表,只有第三個(gè)列表元素有 layout。我們會(huì)看到這樣:
1… 2… 1… 4… 5…
此外,如果一個(gè)有 layout 的列表元素跨行顯示時(shí),列表符號(hào)會(huì)底部對(duì)齊(而不是按照預(yù)料的頂部對(duì)齊)。
以上某些問題還是無法解決的,所以如果需要列表符號(hào)的時(shí)候最好避免讓列表和列表元素獲得 layout。如果需要限定尺寸,最好給別的元素設(shè)定尺寸,比如給整個(gè)列表外面套一個(gè)元素并設(shè)定它的寬度,又或者比如給每個(gè)列表元素中的內(nèi)容設(shè)定高度等等。
另一個(gè)IE中列表的常見問題出現(xiàn)在當(dāng)某個(gè) li
中的內(nèi)容是一個(gè) display: block
的錨點(diǎn)(anchor)時(shí)。在這種情況下,列表元素之間的空格將不會(huì)被忽略而且通常會(huì)顯示成額外的一行夾在每個(gè) li
之間。一種避免這種豎直方向多余空白的解決方法是賦予這些錨點(diǎn) layout。這樣還有一個(gè)好處就是可以讓整個(gè)錨點(diǎn)的矩形區(qū)域都可以響應(yīng)鼠標(biāo)點(diǎn)擊。
表格
table
總是有 layout 的,它總表現(xiàn)為一個(gè)已定義寬度的對(duì)象。在IE6中,table-layout: fixed
通常和一個(gè)寬度設(shè)為100%的表格相同,同時(shí)這也會(huì)帶來很多問題(一些計(jì)算方面的錯(cuò)誤)。另外在IE5.5和IE6的quirks模式下還有一些別的需要注意的情況。
相對(duì)定位元素(r.p.)
注意,由于 position: relative
并不觸發(fā) hasLayout,所以很多諸如內(nèi)容消失或錯(cuò)位的渲染錯(cuò)誤就會(huì)因此而起。這些現(xiàn)象可能會(huì)在刷新頁面、調(diào)整窗口大小、滾動(dòng)頁面、選中內(nèi)容等情況下出現(xiàn)。原因是 IE 在據(jù)這個(gè)屬性對(duì)元素做偏移處理時(shí),卻似乎忘了發(fā)出信號(hào)讓其 layout 孩子元素進(jìn)行“重繪”(而如果是一個(gè)layout元素,那么在其重繪事件的信號(hào)鏈中,這個(gè)傳給其孩子的信號(hào)是會(huì)正常發(fā)出的)。
- r.p. parent and disappearing floated child
- disappearing list-background bug
以上是一些相關(guān)問題的描述。作為經(jīng)驗(yàn)之談,相對(duì)定位一個(gè)元素時(shí)最好給予其 layout。再有,我們也需要檢查擁有這種結(jié)構(gòu)的父元素是否也需要 layout 或者position: relative
亦或二者都需要,如果涉及到浮動(dòng)元素這點(diǎn)就十分重要。
絕對(duì)定位元素(a.p.):
包含區(qū)塊,什么是包含區(qū)塊?
理解 CSS 的包含區(qū)塊概念很重要,它回答了絕對(duì)定位元素是相對(duì)哪里定位的問題:包含區(qū)塊決定了偏移起點(diǎn),包含區(qū)塊定義了百分比長(zhǎng)度的計(jì)算參考。
對(duì)于絕對(duì)定位元素,包含區(qū)塊是由其最近的定位祖先決定的。如果其祖先都沒有被定位,那么就使用初始包含區(qū)塊 html
。
通常情況下我們會(huì)用 position: relative
來設(shè)定任意包含區(qū)塊。這就是說,我們可以讓一個(gè)絕對(duì)定位元素所參考的原點(diǎn)和長(zhǎng)度等不依賴于元素的排列順序,這可以滿足諸如“內(nèi)容優(yōu)先”這種可訪問性概念的需要,也可以給復(fù)雜的浮動(dòng)布局帶來方便。
但是由于 layout 概念的存在,這種設(shè)計(jì)理念的效果在IE中就要打個(gè)問號(hào)了。因?yàn)樵贗E中絕對(duì)定位的元素是相對(duì)于其最近的 layout 定位祖先而做偏移的,而百分比的尺寸卻是參考這個(gè) layout 定位祖先的下一個(gè) layout 祖先計(jì)算的。注意這里的小差別,還有剛才提到 position: relative
是不會(huì)觸發(fā) hasLayout 的。
假設(shè)一個(gè)無 layout 的父元素被相對(duì)定位了——我們就得給它賦予 layout 才能使偏移量起作用:
假設(shè)一個(gè)未定位的父元素需要特定尺寸,而且頁面設(shè)計(jì)是基于百分比寬度的——我們就可以放棄這個(gè)想法了,因?yàn)闉g覽器支持不佳:
- absolutely positioned element and percentage width
濾鏡
MS專有的濾鏡屬性 filter 是只適用于 layout 元素的。有些濾鏡擴(kuò)展了對(duì)象的邊界。它們會(huì)顯示出自身特有的缺陷。
對(duì)已渲染元素的重排(re-flow)
當(dāng)所有元素都已渲染完成時(shí),如果有一個(gè)因鼠標(biāo)經(jīng)過而引起的變化產(chǎn)生(比如某個(gè)鏈接的 background
有變化),IE會(huì)對(duì)其 layout 包含區(qū)塊進(jìn)行重排。有時(shí)一些元素就會(huì)因此被排到了新的位置,因?yàn)楫?dāng)這個(gè)鼠標(biāo)經(jīng)過發(fā)生時(shí),IE已經(jīng)知道了所有相關(guān)元素的寬度、偏移量等數(shù)據(jù)了。這在文檔首次載入時(shí)則不會(huì)發(fā)生,那時(shí)由于自動(dòng)擴(kuò)張的特性,寬度還無法確定。這種情況會(huì)導(dǎo)致在鼠標(biāo)經(jīng)過時(shí)頁面出現(xiàn)跳變。
- Jump on :hover
- quirky percentages: the reflow
這些和重排問題相關(guān)的 bug 會(huì)給百分比邊距和補(bǔ)白使用較多的流動(dòng)布局帶來不少麻煩。
背景原點(diǎn)
MS專有的這個(gè) hasLayout 還會(huì)影響背景的定位和擴(kuò)展。比如,根據(jù) CSS 規(guī)范,background-position: 0 0
應(yīng)該指元素的“補(bǔ)白邊緣(padding edge)”。而在 IE/Win 下,如果 hasLayout = false
則指的是“邊框邊緣(border edge)”,當(dāng) hasLayout=true
時(shí)指的才是補(bǔ)白邊緣:
- Background, Border, hasLayout
邊距重疊
hasLayout
會(huì)影響一個(gè)盒子和其子孫的邊距重疊。根據(jù)規(guī)范,一個(gè)盒子如果沒有上補(bǔ)白和上邊框,那么它的上邊距應(yīng)該和其文檔流中的第一個(gè)孩子元素的上邊距重疊:
- Collapsing Margins
- Uncollapsing Margins
在 IE/Win 中如果這個(gè)盒子有 layout 那么這種現(xiàn)象就不會(huì)發(fā)生了:似乎擁有 layout 會(huì)阻止其孩子的邊距伸出包含容器之外。此外當(dāng) hasLayout = true
時(shí),不論包含容器還是孩子元素,都會(huì)有邊距計(jì)算錯(cuò)誤的問題出現(xiàn)。
- Margin collapsing and hasLayout
塊級(jí)別的鏈接
hasLayout 會(huì)影響一個(gè)塊級(jí)別鏈接的鼠標(biāo)響應(yīng)區(qū)域(可點(diǎn)擊區(qū)域)。通常 hasLayout = false 時(shí)只有文字覆蓋區(qū)域才能響應(yīng)。而 hasLayout = true 則整個(gè)塊狀區(qū)域都可響應(yīng)。添加了 onclick/onmouseover 等事件的任意塊級(jí)元素也有同樣的現(xiàn)象。
- Block anchors and hasLayout
在頁面內(nèi)使用鍵盤瀏覽:探索中
當(dāng)使用 tab 在頁面中瀏覽時(shí),如果進(jìn)入了一個(gè)頁內(nèi)鏈接(in-page link),那么接下來再按的 tab 鍵就不會(huì)正常繼續(xù)了:
- hasLayout Property Characterizes IE6 Bug
- Keyboard Navigation and Internet Explorer
tab 鍵會(huì)把用戶帶到(這通常是錯(cuò)誤的)其最近的 layout 祖先中的第一個(gè)目標(biāo)(如果這個(gè)祖先是由 table
, div
, span
或某些別的標(biāo)簽構(gòu)成)。
堆疊,分層和 layout
IE/Win 中似乎有兩種分層和堆疊順序:
- 一種是(偽)試圖采用CSS的模式:Effect of z-index value to RP and AP blocks
- 還有一種是由“hasLayout”及其孿生兄弟“contenteditable”的行為產(chǎn)生的堆疊順序。正如在上面相對(duì)定位的例子中展現(xiàn)的那樣,在 layout 影響下的堆疊現(xiàn)象就好像 Harry Houdini (譯者注:魔術(shù)師,以紙牌魔術(shù)成名)的拿手戲法兒一樣。
兩種堆疊模式雖互不相容,但卻共存于IE的渲染引擎中。經(jīng)驗(yàn)之談:調(diào)試的時(shí)候,兩種情況都要考慮到。我們可能會(huì)有規(guī)律地在下拉菜單或者類似的復(fù)雜菜單中看到相關(guān)問題,因?yàn)樗鼈兺鶢可娴蕉询B,定位和浮動(dòng)等諸多令人頭疼的問題。給那些 z-index 定位的元素 layout 是一種可能的修正方法,不過也不限于此,這里只是提醒一下。
混亂的 contenteditable
如果給一個(gè) HTML 標(biāo)簽設(shè)定 contenteditable=true
屬性,比如body contenteditable=true>
,將會(huì)允許對(duì)該元素以及其 layout 子元素進(jìn)行實(shí)時(shí)的編輯、拖動(dòng)改變尺寸等操作。你可以把這屬性用在浮動(dòng)元素或者一個(gè)有序列表中的 layout 列表元素上看看效果。
為了對(duì)元素進(jìn)行操作(編輯它們),“contenteditable”和“hasLayout”為那些 hasLayout
返回 true
的元素引入了一套單獨(dú)的堆疊順序。
Editing Platform 繼承了 layout 概念,對(duì) layout 的誤解多是因 contenteditable 而起即可作為證明(那些某種程度上集成了IE編輯引擎的應(yīng)用軟件多暗含著對(duì)layout概念的某種強(qiáng)制向后兼容性)。
和 CSS 規(guī)范類似的地方
你的 MSIE 頁面在別的瀏覽器中一團(tuán)糟?我們可沒必要讓這種事情發(fā)生。如果使用恰當(dāng),任何好的瀏覽器都能擺平 MSIE 的頁面——只要你使用一些正確的 CSS。
利用 hasLayout
和“新的塊級(jí)格式內(nèi)容”之間的細(xì)微相似之處,我們可以有幾種方法在標(biāo)準(zhǔn)兼容瀏覽器中重新實(shí)現(xiàn) hasLayout
的“包含浮動(dòng)元素”效果,和一些“浮動(dòng)元素旁邊的元素”所特有的效果。
- Reverse engineering series
- Simulations
Quirks 模式
某些 doctype,或者 xml>
聲明,在 IE6 中會(huì)觸發(fā)“quirks模式”或曰向后兼容模式。在這種模式下,IE6 就像 IE5.5,并且和它老弟擁有一樣的bug,一樣的問題和一樣的行為。
而對(duì)于IE7,xml>
聲明不會(huì)再改變渲染模式了;要觸發(fā) quirks 模式,我們不得不插入一個(gè)注釋才行。(IE7 的 quirks 模式和 IE6 的 quirks 模式是否一樣還有待驗(yàn)證)
?xml version="1.0" encoding="utf-8"?>
!-- ... 讓 IE7 運(yùn)行在 quirks 模式 -->
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
Layout — 結(jié)論
整個(gè) layout 概念和一些基本 CSS 概念是不兼容的,即包含,排列,浮動(dòng),定位和層疊等。
由于頁面中元素或有或沒有 layout,會(huì)導(dǎo)致 IE/Win 的行為和 CSS 規(guī)范相違背。
擁有 layout — 另外一個(gè)引擎?
IE 的對(duì)象模型看起來是文檔模型和他們傳統(tǒng)的應(yīng)用程序模型的糅合。我之所以提到這點(diǎn)是因?yàn)樗鼘?duì)于理解IE如何渲染頁面很重要。而從文檔模型切換到應(yīng)用程序模型的開關(guān)就是給一個(gè)元素“l(fā)ayout”。
(Dean Edwards)
有時(shí)候要解釋清楚某種行為是不可能的:就比如 hasLayout,會(huì)根據(jù)它的狀態(tài)選擇兩種不同渲染引擎的一種使用,而且每一種都有其自己的 bug 和怪異之處。
不可消除的 bug
軟件 bug 是由于在制作過程中對(duì)完整性和邏輯問題考慮不周等人為錯(cuò)誤而導(dǎo)致的。這是人類的固有缺陷,目前還沒有什么好的解決方法。
同樣由于這種缺陷,任何試圖不重寫軟件而修復(fù) bug 的做法,都將會(huì)不可避免的導(dǎo)致軟件中出現(xiàn)更復(fù)雜的bug。
所有依賴別的軟件的軟件——當(dāng)然包括依賴操作系統(tǒng),也會(huì)同樣依賴他們的 bug。于是我們會(huì)從所有關(guān)聯(lián)的軟件中得到一連串的 bug,這也更說明找到一個(gè)無 bug 軟件是幾乎不可能的。
(Molly, the cat?)
本文創(chuàng)建于2005年6月30日,最后一次修改于2006年4月2日。