原文地址。css
( 譯者注:這是一篇深度好文,而且附帶官方簡體中文。本次的翻譯爲一人完成,限於水平,不免有誤,故須要學習本文內容的同窗請直接參考原文地址進行閱讀。html
導讀: 終於,我在一週以內講這長篇大論的瀏覽器背後的故事翻譯完了。若是再要我從新閱讀一遍,可能須要我沐浴焚香般的準備。如今我忍着肩膀和手腕的痠痛,寫下發布前的最後一些體會:html5
這是篇全面介紹 WebKit 和 Gecko 的內部操做的文章,它是以色列的開發者 Tail Garsiel 的大量的研究成果。過去幾年,她從新審視了已公開的關於瀏覽器內部的資料(參考資料)同時花費了不少時間去閱讀 web 瀏覽器源碼。她寫道:node
在 IE 90% 支配的那個年代,把瀏覽器當作一個「黑盒」再也合適不過了,可是如今,開源瀏覽器佔據了一半的市場份額,是時候去了解引擎的背後同時看看 web 瀏覽器的內部。儘管,裏面有數百萬行 C++ 代碼……c++
Tail 在她的網站上發佈了她的研究,可是咱們想讓更多的人知道,因此咱們已經從新整理再次發佈在這裏。web
做爲一個 web 開發,瞭解瀏覽器操做的內部會幫助你作出更好的決定,同時在最佳開發實踐時瞭解充分的理由。而這是一篇有長度的文章,咱們推薦你花點時間去深挖探究,咱們保證你會對本身的所作滿意。 Paul lrish, Chrome 開發人員關係部正則表達式
這篇文章被翻譯成幾種語言:HTML5 Rocks 翻譯了 德語,西班牙語,日語,葡萄牙語,俄語和簡體中文版本。你也能夠看到韓語和土耳其語。 你也能看看關於這篇主題 Tail Garsiel 在 Vimeo 上的談話。算法
(譯者注:這篇目錄翻譯了我半個小時,經過目錄的回顧確實跟以前的一些零碎知識串聯了起來,發現不少。更主要的是,跑個題來緩解下被這個目錄嚇尿的心臟。)express
Web 瀏覽器是使用最普遍的軟件。這篇讀物中,我會解釋在場景以後他們是如何工做的。咱們將會看到,當你在地址欄輸入 google.com 時直到在瀏覽器屏幕上看到 Google Page 頁面後,發生了什麼。canvas
現在經常使用的主要瀏覽器有 5 種: Chrome,IE,火狐,Safari 和 Opera。在移動端上,主要的瀏覽器是安卓瀏覽器,蘋果,Opera 迷你和 Opera移動端還有 UC 瀏覽器,諾基亞 S40/S60 瀏覽器和 Chrome 也都是,除了 Opera 瀏覽器,其餘都是基於 WebKit。(譯者注:前一句話在官方簡體中文裏沒有.)我從開源瀏覽器火狐和 Chrome 以及 Safari(部分開源)中距離。根據 StatCounter 統計(從2013年6月以來) Chrome 火狐和 Safari 組成了全球桌面瀏覽器使用量的 71%。在移動端,安卓瀏覽器,iPhone 和 Chrome 有 54% 的使用率。
瀏覽器的主要功能是展現你選擇的 web 資源,經過服務端的請求而後在瀏覽器窗口展現。這個資源一般是一個 HTML 文檔,但也有多是一個 PDF,一張圖片,或者其餘類型的內容。資源的位置經過用戶使用 URI(Uniform Resource Identifier) 來明確指出。
瀏覽器插入和展現 HTML 文件的方式在 HTML 和 CSS 規範中有詳細說明。這些規範經過 W3C(World Wide Web Consortium) 維護,這些規範也是 web 的標準組織。這些年的瀏覽器只是遵照一部分標準同時開發了他們本身的擴展。這對 web 開發者來講引起了一系列的兼容性問題。現在大多數瀏覽器或多或少遵照這些規範。
瀏覽器用戶界面相互有不少共同之處。它們之間的共同元素有:
奇怪的是,瀏覽器的用戶界面沒有任何形式的規範,它只是從過去幾年的經驗和經過瀏覽器相互模仿中產生的最佳實踐。 HTML5 規範沒有定義一個瀏覽器必須擁有的 UI 元素,可是列出了一些常見元素。它們有地址欄,狀態欄和工具欄。這些內容,尤爲是,像火狐的下載管理器對具體瀏覽器而言是特有的。
瀏覽器的主要組件有(1.1):
對於瀏覽器而言這是很重要的,好比 Chrome 運行多個渲染引擎的實例爲每個標籤。每一個標籤有個獨立的進程。
渲染引擎的責任是,額……渲染,也就是在瀏覽器屏幕上展現請求的內容。
默認的渲染引擎能夠展現 HTML 和 XML 文檔以及圖片。它也能夠經過插件或者擴展來展現其餘的數據類型。舉個例子,使用 PDF 視圖插件展現 PDF 文檔。然而,在本章節中咱們將關注它的主要用處:展現使用 CSS 格式化的 HTML 和 圖片。
不一樣的瀏覽器使用不一樣的渲染引擎:IE 使用 Trident,火狐使用 Gecko,Safari 使用 WebKit。Chrome 和 Opera(自 15 版)使用 Blink,是Webkit 的一個分支。
WebKit是一個開源引擎,做爲引擎,最開始在 Linux 平臺上而後被 Apple 爲了支持 Mac 和 Windows而修改。瞭解webkit.org的更多細節。
渲染引擎從網絡層開始獲取請求文檔的內容。這個一般在一個 8KB 塊中完成。
在那以後,展現了渲染引擎的基本流程:
渲染引擎開始解析 HTMl 文檔同時在一個名叫「內容樹」的樹中轉化元素變成 DOM 節點。引擎將會解析樣式數據,外部的 CSS 文件和元素樣式。在 HTML 中帶有可視指令的樣式信息將會被用於建立另外一個樹:渲染樹。
渲染樹包含了有可視屬性像是顏色和尺寸的矩形。這個矩形在屏幕上以正確的順序展現。
以後的渲染樹會經過一個「佈局」進程。這意味着該進程會給在屏幕上應該出現的每一個節點一個精確的座標。下一個階段是繪製——渲染樹被轉換而後每一個節點經過 UI 後臺層被繪製。
理解這個漸進過程是很是必要的。爲了更好地用戶體驗,渲染引擎會盡快嘗試在屏幕上展現內容。它在開始構建和佈局渲染樹以前,不會等待全部的 HTMl 被解析。一部份內容被解析和展現,而進程繼續剩餘的從網絡來的內容。
從圖 3 和圖 4你能夠看到,WebKit 和 Gecko 術語上有點不一樣,過程仍是基本同樣的。
Gecko 調用一個樹,這個樹是可視化的一個被格式化的「框架樹」。每一個元素是一個框架。WebKit 使用的叫作「Render Tree」,同時它由「Render Objects」組成。WebKit 對元素位置使用「Layout」,而 Gecko 稱它爲 「Reflow」。「Attachment」是WebKit一個術語,用於鏈接 DOM 節點和可視化信息用於建立渲染樹。一個不重要的非語義的不一樣是 Gecko 在 HTML 和 DOM 樹之間有一個額外的層,叫作 「content sink(內容沉澱)」,同時它是一個製做 DOM 元素的工場。咱們將會討論過程的每一個部分:
由於在渲染引擎中,解析是很是明顯的進程,咱們將會探索的深刻一點。經過關於解析的簡單介紹來開始。
解析一個文檔意味着翻譯爲代碼可用的結構。解析的結果一般是一個節點樹,這顆樹表明着文檔結構。一般叫作解析樹或者語法樹。
舉個例子,解析表達式 2 + 3 -1
能夠返回下面的樹:
解析基於文件遵照的語法規則:語言或者寫入的格式。全部能夠解析的格式必須由詞彙和句法規則構成肯定的語法。這稱爲上下文無關語法。人類語言不是這種語言,所以不能用常規的解析技術解析。
解析能夠分爲兩個獨立的子過程:詞法分析和語法分析。
詞法分析是將輸入變成爲標記的過程。標記是語言詞彙:構建塊的集合。在人類語言中它由全部在這個語言的字典中出現的單詞構成。
語法分析是語言語法規則的應用。
解析一般在兩個部分中獨立的工做:詞法分析器(有時也叫標記分析器),負責將輸入變成有效地標記,同時解析器的責任是經過根據語言規則來分析文檔構建解析樹。詞法分析器知道如何去除不相關的字符好比空格和換行。
解析過程是反覆的。解析器一般爲新的標記向詞法分析器請求,並嘗試將標記與某條語法規則匹配。若是規則匹配了,一個相應標記的節點將被添加到語法樹中去,而且解析會請求下一個標記。
若是沒有規則匹配,解析器會內部儲存這個標記,而且保持請求標記直到一個匹配到全部儲存在內部的標記的規則被發現。若是沒有找到規則,那麼解析器將拋出一個錯誤。這意味着文件無效而且包含語法錯誤。
在不少例子中,解析樹不是最終產物。解析一般用於翻譯:轉換輸入文檔爲另外一種格式。舉個例子好比編譯:編譯器編譯源碼成爲機器碼,首先編譯成編譯樹,而後再編譯成機器碼文件。
在圖表5中,咱們從數學表達式中構建編譯樹。咱們試試定義一個簡單的數學語言而後看看編譯過程。
詞彙:咱們的語言包括數字,加減號。 語法:
咱們來分析輸入的: 2 + 3 - 1
首先匹配到的規則串是 2
:根據規則 #5 這是一個項。第二個匹配是 2 + 3
:這個匹配了第三個規則:一個項連接着一個連接另外一個項的操做符。下一個匹配將在輸入的最後。2 + 3 -1
是一個表達式,由於咱們知道, 2 + 3
是一個項,因此咱們有一個項,經過連接另外一個項的操做符連接着。2 + +
不會匹配任何規則,所以是一個無效的輸入。
詞彙一般經過正則表達式表現。
舉個例子,咱們的語言將被定義爲以下:
INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -
複製代碼
正如你看到的,整型經過正則表達式定義。
語法一般經過一種叫作 BNF 的格式定義。咱們的語言將被定義爲以下:
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
複製代碼
咱們認爲,若是一種語言的語法是上下文無關的,那麼它能夠經過常規解析器解析。上下文無關語法的直觀定義是能夠在 BNF 中被徹底表達的語法。一種形式的定義是關於上下文無關文法的維基百科的文章。
這裏有兩種解析類型:自頂向下和自底向上的解析。直觀的解釋是,自頂向下的解析檢查上層語法結構而後嘗試找到規則匹配。自底向上從輸入開始,逐級轉化成語法規則,從底層規則開始直到遇到頂層規則。
咱們看看這兩種解析類型如何解析咱們的例子。
自頂向下解析從上層開始:它將識別 2 + 3
爲一個表達式。而後識別 2 + 3 - 1
爲一個表達式(識別表達式的過程是逐步的,匹配其餘規則的,可是是從上層開始。)
自底向上解析將會瀏覽輸入直到一個規則被匹配。它將用這個規則替換匹配。這會一直執行直到輸入的末端。部分匹配到的表達式在解析棧上被替換。
Stack | Input |
---|---|
term | 2 + 3 -1 |
term operation | + 3 - 1 |
expression | 3 - 1 |
expression operation | - 1 |
expression | - |
這種自底向上的解析稱爲移入規約解析,由於輸入偏移到右側(想象一個指針在輸入開始而後移動到右側),而且逐漸減小語法規則。
有個工具能夠生成解析。你告訴工具你的語言語法——它的詞彙和語法規則——而後工具會生成一個有效解析。建立解析須要深入理解解析,而且手動建立一個優化的解析並不容易,因此解析生成器是頗有用的。
WebKit 使用兩個著名的解析工具: Flex,用於建立詞法,Bison,用於建立解析(你會或許把他們稱爲 Lex 和 Yacc)。Flex 輸入是一個文件,包含標記的正則表達式定義。Bison 的輸入是在 BNF 格式中的語言與法。
HTML 解析器的工做是把 HTML 標記轉化成解析樹。
HTML 的詞彙和語法由 W3C 組織創造,在這個規範被定義。
如同咱們在解析中的介紹,語法能夠像 BNF 那樣使用格式化的格式被定義。
不幸的是,全部常規的解析方式不能應用於 HTML(爲了好玩我不把它們如今引入——它們在解析 CSS 和 JavaScript時將被用到)。HTML 不能像解析器須要的那樣經過上下文無關文法被定義。
乍一看這個表現很奇怪; HTML 十分像 XML。有很是多的 XML 解析器可使用。HTML 是 XML 的變體——因此有什麼很大的不一樣嗎?
這裏的不一樣在於,HTML 儘量的更多「包容」:它能讓你省略某些標籤(那些被隱式添加的),或者有時候省略開始或結束標籤等等。總體來看它是「軟性語法,與 XML 的嚴格硬性語法不一樣。
HTML 定義在一種 DTD 格式裏。這種格式用於定義 SGML 家族的語言。這個格式爲全部容許的元素,它們的屬性和等級定義。咱們以前看到,HTML DTD 不是一種上下文無關文法。
DTD 有一些變體。嚴格模式適用於惟一的規範,可是其餘模式包含對過去瀏覽器使用的標記的支持。這個目的是能夠向後兼容老舊的內容。目前嚴格模式的 DTD 參考這裏www.w3.org/TR/html4/st…。
輸出樹(解析樹)是 DOM 元素和節點屬性的樹。DOM 是 Document Object Model 的縮寫。它是 HTML 文檔的表現對象和 HTML 元素對外部世界像是 JavaScript 元素的接口。樹的根節點是 「Document」 對象。
DOM 對標記來講幾乎要一對一的關係。舉個例子:
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
複製代碼
標記將會被轉化爲下面的 DOM 樹:
像 HTML,DOM 經過 W3C 組織定義。參考這裏www.w3.org/DOM/DOMTR。它是對操做文檔的通常定義。特殊的模型描述了 HTML 特殊元素。HTML 定義能夠在這裏找到:www.w3.org/TR/2003/REC…。
當我談到樹包含 DOM 節點時,個人意思是這棵樹是有結構的元素,實現了 DOM 其中之一的接口。瀏覽器混合了這些實現,這些實現有一些經過瀏覽器內部定義的其餘屬性。
如咱們以前看到的部分同樣,HTML 不能使用常規的自頂向下或者自底向上解析。
緣由有:
document.write()
的腳本元素調用)能夠添加額外的標記,因此解析過程實際上修改了輸入。不能使用常規解析技術,瀏覽器爲解析 HTML 建立了自定義解析。
由 HTML5 規範定義瞭解析算法的細節。算法有兩個階段組成:標記(斷詞)和結構樹。
標記是詞法分析,解析是輸入變成標記。在 HTML 中,標記是開始標籤,結束標籤,屬性名和屬性值。
標記器識別標記,把標記給樹構造器,而且爲下個識別的標記處理下個字符,直到輸入的結尾。
這個算法的輸出是 HTML 標記。這個算法被做爲狀態機表達。每一個狀態使用一個或者多個輸入流的字符,而且根據這些字符更新下一個狀態。這個決定經過當前標記狀態和樹構造狀態影響。這就意味着消耗一樣的字符爲了正確的下個狀態將會產出不一樣的結果,這取決於當前狀態。這個算法過於複雜,以至不能徹底描述,咱們來看看一個簡單的例子,這能夠幫助咱們理解這個規則。
基本例子:標記如下 HTML:
<html>
<body>
Hello world
</body>
</html>
複製代碼
初始化狀態是 「Data State」。當遇到 <
字符時,狀態變成「Tag open state」。使用 a-z
的字符產生「Start tag token」的建立,狀態變爲「Tag name state」。咱們保留這個狀態直到 >
字符出現。每一個字符都被添加到新的標記名稱上。在咱們的例子中,這個建立的標記是 html
標記。
當 >
標籤出現,當前標記被髮送,同時狀態變回 Data state
。<body>
標籤也是用相同的步驟處理。目前爲止,html
和 body
標籤被髮送了。咱們如今回到了 「Data state」。遇到 Hello world
字符的 H
將會引發建立和字符標記的發送,這將一直進行直到碰見 </body>
的 <
。咱們將爲 Hello world
的每個字符發送一個字符標記。
如今咱們回到「Tag open state」。遇到下一個輸入 /
將會引發結束標籤的建立,而且移動到「Tag name state」。再一次咱們保持在這個狀態,直到咱們碰見 >
。此時這個新的標籤標記將被髮送,而且咱們回到「Data state」。</html>
輸入將像以前的例子同樣被處理。
當建立文檔對象的解析器被建立。在樹構造階段期間,以 Document 爲根節點的 DOM 樹也被修改,而且元素被添加進去。經過標記生成器發送的每一個節點將被樹構造器處理。對於每一個標記,規範定義了 DOM 元素與之相關,同時有一個開放元素的棧。這個棧用於正確嵌套沒有匹配和沒有閉合的標籤。這個算法也是做爲一個狀態機描述。這個狀態叫作「insertion modes」(插入模式)。
咱們看看樹構造過程輸入的例子:
<html>
<body>
Hello world
</body>
</html>
複製代碼
從標記階段中,輸入給樹構造器的階段是連續的標記。初始模式是 「initial mode」。接收到 「html」 標記將會移動到 「before html」 模式,而且在那個模式中再處理標記。這將引起 HTMLHtmlElement 元素建立,它將被添加到 Document 對象的根節點中。
狀態將被變爲 「before head」。「body」 標記被接受時。HTMLHeadElement 將被隱式建立,儘管咱們沒有 「head」 標記,而且它會被添加到樹中。
如今咱們移動到 「in head」 模式,而且將會到 「after head」。body 標記是再次執行的,HTMLBodyElement 被建立和插入,模式被轉換爲 「in body」。
「Hello world」 字符串的字符標記如今接受了。首先會發生建立和 「Text」 模式的插入,而且其餘字符也將加入到這個節點中。
body 結束標記引發到 「after body」 模式的轉換。咱們會收到 html 結束標記,它將會移動到 「after after body」 模式。接受文件標記的結束將會結束解析過程。
在這個階段,瀏覽器將會做爲交互而標記文檔,而且開始解析在 「deferred」 模式下的腳本:這些本應該在文檔解析後被解析。文檔狀態將被設置爲 「complete」而後一個 「load」 事件將被觸發。
在這裏能看到 HTML5 規範中標記器和樹構造器的完整算法
你不會在 HTML 頁面獲得一個 「無效語法」 的錯誤。瀏覽器會修復任何無效的內容,而後繼續。
好比下面的例子:
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</p>
</html>
複製代碼
我必定要違反不少規則(「mytag」 不是個標準標準標籤,「p」 和 「div」 標籤的錯誤嵌套等等)可是瀏覽器仍然正確展現,而且沒有任何抱怨。覺得不少解析代碼在修復 HTML 做者的錯誤。
錯誤處理是十分一致的,但吃驚的是,它不是 HTML 規範的部分。如同書籤和後退前進按鈕同樣,它只是這些年在瀏覽器中被髮開出來。不少網站上有不少無效的 HTML 結構重複着,而且瀏覽器嘗試用一種與其餘瀏覽器同樣的方式修復。
HTML 規範定義了一些要求。(WebKit 在 HTML 解析器類的開始的註釋很好的總結了)
解析器解析標記輸入成爲文檔,構建文檔樹。若是文檔格式良好,解析會直接進行。
不幸的是,咱們不得不處理不少 HTML 文檔,那些文檔沒有很好的格式,因此解析器不得不容忍這些錯誤。
咱們能夠了解到至少如下幾種錯誤條件:
1. 在某些標籤外部,明確禁止添加元素。這種狀況下,咱們應該關閉全部的標籤,直到一個禁止的標籤出現,以後添加它。
2. 咱們不容許直接添加元素。它多是人們寫入文檔忘記的標籤(或者其中的標籤是可選的)。好比如下標籤:HTML HEAD BODY TBODY TR TD LI(漏了什麼嗎?)
3. 咱們想在行內元素中添加塊元素。閉合全部的行內元素直到下一個更高級的塊元素出現。
4. 若是這些都沒有做用,直到容許咱們添加或者忽略標籤纔會閉合元素。
複製代碼
咱們來看看 WebKit 的容錯例子:
有些網站使用 </br>
而不是 <br>
。爲了兼容 IE 和 火狐, WebKit都當作 <br>
。
代碼以下:
if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
reportError(MalformedBRError);
t->beginTag = true;
}
複製代碼
注意內部的錯誤處理:這不會展現給用戶。
交叉表格是一個表格內部有另外一個表格,可是不在單元格里。
好比:
<table>
<table>
<tr><td>inner table</td></tr>
</table>
<tr><td>outer table</td></tr>
</table>
複製代碼
WebKit 將會改變兩個子表的層級:
<table>
<tr><td>outer table</td></tr>
</table>
<table>
<tr><td>inner table</td></tr>
</table>
複製代碼
代碼以下:
if (m_inStrayTableContent && localName == tableTag)
popBlock(tableTag);
複製代碼
WebKit 爲當前元素內容使用一個棧:它將彈出外部表格棧的內部表格。如今這個表格成了兄弟關係。
在用戶輸入一個表單內部中包含另外一個表單時,第二個表單將被忽略。
代碼以下:
if (!m_currentFormElement) {
m_currentFormElement = new HTMLFormElement(formTag, m_document);
}
複製代碼
註釋不言而喻。
www.liceo.edu.mx 是一個網站的例子,這個網站簽到了大約 1500 個標籤層級,全部這些來自
<b>
分支。在所有忽略它們以前,咱們最多容許 20 個同類型的嵌套標籤。
bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
{
unsigned i = 0;
for (HTMLStackElem* curr = m_blockStack;
i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
curr = curr->next, i++) { }
return i != cMaxRedundantTagDepth;
}
複製代碼
再次參考註釋:
爲了支持被破壞的 HTML。咱們永遠不會閉合 body 標籤,由於有些愚蠢的網站頁面在文檔真正結束以前閉合了它。咱們依賴在 end() 調用上閉合它們。
if (t->tagName == htmlTag || t->tagName == bodyTag )
return;
複製代碼
因此 web 做者意識到——除非你想去表現一個 WebKit 容錯代碼片斷做爲例子——不然就寫良好格式化的代碼。
還記得介紹裏面的解析概念嗎?好吧,像 HTML,CSS 是上下文無關語法,而且能夠在介紹中使用解析類型定義類型定義來解析。事實上 CSS 規範定義了 CSS 詞法和語法規則。
咱們來看看幾個例子: 下面的詞法規則(詞彙)經過正則表達式爲每一個標記定義。
comment \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num [0-9]+|[0-9]*"."[0-9]+
nonascii [\200-\377]
nmstart [_a-z]|{nonascii}|{escape}
nmchar [_a-z0-9-]|{nonascii}|{escape}
name {nmchar}+
ident {nmstart}{nmchar}*
複製代碼
"ident" 是 identifier的 縮寫,相似類名。「name」 是一個元素的 id(也被記做「#」)
語法在 BNF 中的描述:
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
selector
: simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
;
simple_selector
: element_name [ HASH | class | attrib | pseudo ]*
| [ HASH | class | attrib | pseudo ]+
;
class
: '.' IDENT
;
element_name
: IDENT | '*'
;
attrib
: '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
[ IDENT | STRING ] S* ] ']'
;
pseudo
: ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
;
複製代碼
解釋:一個規則集合如這樣的結構:
div.error, a.error {
color:red;
font-weight:bold;
}
複製代碼
div.error
和 a.error
是選擇器。花括號裏面的部分包含這個規則,它們在規則集被應用。這個結構在定義中被形式地定義爲以下:
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
複製代碼
也就是說規則集是選擇器或可選擇地經過逗號和空格(S 表明空格)被一系列選擇符分割。一個規則集包含花括號和內部的描述或者可選的逗號分割。
WebKit 使用 Flex 和 Bison。如同從解析介紹中回憶起來的同樣,Bison 建立了自底向上遞歸降低解析。火狐使用了自頂向下手工寫入。這兩種例子的每一個 CSS 文件會被解析成一個 StyleSheet 對象。每一個對象包含 CSS 規則。這個 CSS 規則對象包含選擇器和對象聲明以及其餘對應 CSS 語法的對象。
web 的模型是同步的。建立者但願當解析器遇到 <script>
標籤被解析後當即執行。文檔解析器停止直到腳本被執行。若是是外部腳本,那麼資源首先必須從網絡請求——這也是同步處理的,同時直到資源得到,不然解析一直停止。這個模型有許多年了,同時在 HTML4 和 HTML5 中被定義。創做者能夠給腳本添加 「defer」 屬性,在這種狀況下,這將不會終止文檔解析,而且在文檔解析後執行腳本。HTML5添加一個可選標記給腳本做爲異步標記,以便未來解析和經過不通線程執行。
Webkit 和 Firefox 都作了這種優化。當腳本執行時,另外一個線程解析剩餘的文檔,而且找出從網絡上須要加載的其餘資源而後加載它們。用這種方式,資源能夠在平行鏈接上加載,而且總的來講速度是提高的。注意:推斷解析只解析外部資源像是外部腳本,樣式表和圖片:它不會修改 DOM 樹——這留給主要解析器。
另外一方面樣式表有着不一樣的模型。概念上來講由於它看起來並不修改 DOM 樹,因此沒有理由去等待他們和中止文檔解析。這裏有個問題,即在文檔解析階段,爲樣式信息的腳本請求。若是樣式沒有加載和解析,腳本將會獲得錯誤答案,而且顯然這會引發一系列問題。這看起來是個邊緣問題,但事實上很常見。當有樣式表仍然在加載和解析的時候,火狐阻止了全部的腳本。WebKit 只會阻止當嘗試去訪問某些樣式屬性,而這些屬性可能被未加載的樣式影響的腳本。
當 DOM 樹被構建時,瀏覽器構建另外一個樹,是渲染樹。這是棵可視元素按照展現順序排列的樹,是可視化文檔的表現。這棵樹的目的是能夠在它們正確的順序下繪製內容。
火狐在渲染樹的 「frames」 中調用元素。 WebKit 使用渲染項或者渲染對象。
渲染知道如何佈局和繪製它本身以及它的後代。
WebKit的 RenderObject 類,渲染的基礎類,有以下定義:
class RenderObject{
virtual void layout();
virtual void paint(PaintInfo);
virtual void rect repaintRect();
Node* node; //the DOM node
RenderStyle* style; // the computed style
RenderLayer* containgLayer; //the containing z-index layer
}
複製代碼
每一個渲染表明一個矩形區域,這個區域一般對應一個 CSS 盒子節點,被 CSS2 規範描述。它包括集合圖形信息像是寬高和位置。
盒子類型被相關節點(參考樣式計算部分)的樣式屬性的 「display」 值影響。WebKit 代碼決定了哪一種渲染類型應該建立爲一個 DOM 節點,根據 display 的屬性:
RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
Document* doc = node->document();
RenderArena* arena = doc->renderArena();
...
RenderObject* o = 0;
switch (style->display()) {
case NONE:
break;
case INLINE:
o = new (arena) RenderInline(node);
break;
case BLOCK:
o = new (arena) RenderBlock(node);
break;
case INLINE_BLOCK:
o = new (arena) RenderBlock(node);
break;
case LIST_ITEM:
o = new (arena) RenderListItem(node);
break;
...
}
return o;
}
複製代碼
元素類型也會考慮:好比,表單控制和有特殊結構的表格。
在 WebKit中,若是元素想去建立特殊渲染,它會覆寫 createRender()
方法。渲染指向樣式對象,這個對象不包含幾何信息。
渲染對應着 DOM 元素,但並非一對一的乾洗。不可見的 DOM 元素不會插入到渲染樹中。舉個例子 「head」 元素。那些 display 值是 「none」 的元素也不會出如今樹中(可是 visibility 是 「hidden」 的元素會出現)。
DOM 元素對應一些可視對象。常見的元素帶有複雜的結構,它們不能經過一個矩形描述。好比: 「Select」 元素有三個渲染:一個用於展現區域,一個用於下拉列表,還有一個用於按鈕。當文字換行時也同樣,由於寬度不知足一行,新的行必須做爲額外的渲染添加。
多行渲染的另外一個例子是破壞的 HTML。根據 CSS 定義,內聯元素要麼只包含塊元素要麼只包含內聯元素。在混合內容的例子中,匿名塊將被建立用於包含內聯元素。
一些渲染對象對應 DOM 節點,可是不在樹中一樣的位置上。浮動和絕對定位元素不在流上,被放在了樹的不一樣部分,映射在真實的結構上。一個佔位結構在它們應該在的位置上。
在火狐中,表現層被當作監聽註冊在 DOM 更新上。表現層給 FrameConstructor
委託框架建立,而且構造器解析樣式(參考樣式計算)和建立框架。
在 WebKit 處理樣式和建立渲染層的過程叫作 「attachment」。每一個 DOM 節點都有一個 「attach」 方法。Attachment 是同步的,節點插入給 DOM 樹調用新節點的 「attach」 方法。
處理 html 和 body 標籤的結果是渲染樹根節點的構造。根渲染對象對應 CSS 中叫作包含塊的規範:最頂部的包含其餘全部塊的塊。它的尺寸是視窗:即瀏覽器窗口的區域尺寸。火狐稱做 ViewPortFram
而 WebKit 稱做 RenderView
。這是文檔指向的渲染對象。其他的樹做爲 DOM 節點插入被構造。
參考 CSS2 規範的過程模型
構建渲染樹須要計算每個渲染對象的可視屬性。這經過計算每一個元素的樣式屬性來完成。
樣式包括各類來源的樣式表,內聯樣式元素和 HTML 中的可視化屬性(像是 「bgcolor」 屬性)。以後被翻譯成匹配 CSS 樣式屬性。
樣式表的來源有,瀏覽器默認樣式,網頁創做者提供的樣式,和用戶樣式——這些樣式表經過瀏覽器使用者提供(瀏覽器容許你定義本身喜歡的樣式。在火狐中,初始化,經過放在 「Firefox Profile」文件夾中的樣式完成)。
樣式計算有點困難:
div div div div {}
複製代碼
意味着這個規則會應用於 3 個 <div>
的後代。假設你想去檢查是否這個規則應用於一個 <div>
元素。你能夠選擇某條樹上向上路徑去檢查。你也能夠傳過節點樹向上發現只有兩個 div,而後規則並不適用。你這是須要去嘗試另外一顆樹。咱們看看瀏覽器如何面對這些問題:
WebKit 節點引用樣式對象(RenderStyle)。這些對象能夠經過節點在相同的條件下共享。這些節點是兄弟或者表兄弟而且:
火狐有兩顆額外的樹用於簡化樣式計算:一顆規則樹和樣式上下文樹。WebKit 也有樣式對象,可是沒有像樣式上下文樹來存儲,只有 DOM 節點指向相關樣式。
樣式上下文包含結束值。這個值被應用在全部正確順序下的匹配規則和實行從邏輯到實際值的轉換操做而計算。舉個例子,若是邏輯值是屏幕上的百分比,它將被計算和轉換成絕對單位。這個規則樹的注意很聰明。它能夠在節點中分享這些值,避免重複計算。這也節約了空間。
全部的匹配規則儲存在一棵樹中。路徑上的底部節點有更高的優先級。樹包含全部的路徑,爲了匹配已經發現的規則。存儲這些規則是懶處理的。樹不會在每一個節點開始的時候計算,但不管什麼時候一個節點樣式須要計算時,計算路徑被添加到樹中。
這個點子看樹像是在詞法中看單詞。咱們看看已經計算的規則樹:
假設咱們須要爲上下文樹的其餘元素匹配規則,而且找出匹配規則(正確的順序)是 B-E-I。咱們已經有在樹中的路徑,由於咱們已經計算出了路徑 A-B-E-I-L。咱們將減小咱們的工做。
來看看樹如何節約咱們的工做。
樣式內容被分割成結構。這些結構包含了樣式信息,像是邊框和顏色這種種類。結構中的全部屬性是繼承或者不繼承的。除非元素定義了繼承屬性,不然從父級繼承。若是沒有定義繼承屬性,使用默認值。
當爲某個元素計算樣式上下文時,咱們首先計算規則樹中的路徑或者使用已經存在的。接着咱們開始在路徑中應用規則去在咱們新的樣式上下文中應用規則。咱們開始從路徑底部節點——這個節點由最高的優先級(一般是最特殊的選擇器)和穿過樹到頂部直到咱們的結構被填滿。若是在規則節點的結構上沒有定義,咱們能夠很好地優化——咱們到樹上直到咱們發現一個節點,是充分定義和簡單指向它——這是最好的優化——真個結構被共享。這節約了末尾值的計算和內存。
若是咱們發現部分定義,咱們到樹上填滿。
若是在結構上找不到任何定義,有些例子中結構是 「繼承」 類型,咱們在上下文樹中指向咱們父級的結構。在有些例子中咱們也成功共享告終構。若是默認值被使用它就是重置結構。
若是大部分節點沒有添加值,那麼咱們須要作一些額外的計算,爲它轉換成實際值。咱們接着在樹節點中緩存結果爲了讓後代使用。
此例中元素有一個兄弟節點,它指向同一個樹節點,那麼所有樣式上下文能夠在它們之間共享。
假定咱們有以下 HTML:
<html>
<body>
<div class="err" id="div1">
<p>
this is a <span class="big"> big error </span>
this is also a
<span class="big"> very big error</span> error
</p>
</div>
<div class="err" id="div2">another error</div>
</body>
</html>
複製代碼
和如下規則:
div {margin:5px;color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}
複製代碼
爲了簡化這些事咱們須要只填滿這兩種結構:顏色結構和邊距結構。顏色結構包括只包括一個成員:顏色。邊距結構包含四個方面。
這個結果規則樹看起來是這樣(節點被節點名稱標記:它們指向規則的數量):
上下文樹將看起來像這樣(節點名:它們指向規則節點):
假設咱們解析 HTML 而且獲得第二個 <div>
標籤。咱們須要爲這個節點建立樣式上下文和填充它的樣式結構。
咱們匹配這些規則而且找出 <div>
匹配規則是 1,2 和 6的。這意味着在樹中已經有存在的路徑,咱們的元素可使用這些路徑,而且咱們爲規則 6 只須要添加另外一個節點給它(在規則樹中的節點 F)。
咱們將建立規則上下文而且在上下文樹中放置。新的上下文內容將會在規則樹中指向節點 F。
咱們須要填充樣式結構。咱們從填滿邊距結構開始。由於最後一個規則節點(F)沒有添加到邊距結構中,咱們能夠在樹上直到找到在以前節點插入的緩存結構而後使用它。咱們將發現它在節點 B 上,它是定義的邊距規則的最高節點。
在第二個 <span>
元素上的工做相對容易。咱們匹配到規則而後得出指向規則 G 的結論,像是以前的 span。由於咱們有一個兄弟節點指向相同的節點,咱們能夠共享所有樣式上下文,而後指向以前 span 的上下文。
對於集成自父級的包含規則的結構,緩存在上下文樹中被處理(顏色屬性其實是繼承,可是火狐當作默認對待而後緩存在規則樹上)。
若是咱們在段落裏爲字體添加規則做爲實例:
p {font-family: Verdana; font size: 10px; font-weight: bold}
複製代碼
接着這個段落元素,它是在上下文樹的div的子代,做爲它的父級它能夠共享相同字體。這是在段落中沒有字體規則定義的狀況。
在 WebKit 中,那些沒有規則樹的,匹配聲明會轉化四次。首先非重要高級優先權屬性被應用(屬性應該是第一次應用由於其餘依賴它們,好比 display),接着高優先級重要的,接着是常規優先級不重要的,最後是常規優先級重要規則。這意味着根據正確的層疊規則屬性出現四次。最後的獲勝。
來總結是:共享樣式對象(所有和部分的內部結構)處理問題在 1 和 3。火狐規則樹也會幫助在正確的順序下應用規則。
這裏有幾個樣式規則的來源:
p {color: blue}
複製代碼
<p style="color: blue" />
複製代碼
<p bgcolor="blue" />
複製代碼
最後兩個對元素來講是簡單匹配,由於它自身的樣式屬性和 HTML 屬性能夠做爲 key 映射到使用的元素。
注意以前的問題 2,CSS 規則匹配比較難辦。爲了解決這個困難,這個規則爲了更容易訪問須要手動操做。
在解析樣式表以後,規則根據選擇器被添加到一個哈希表上。這個表經過 id,類名,標籤名和一般不屬於上述類別的規則來映射。若是選擇器是 id,規則被添加到 id 映射,若是是類,被添加到類映射等等。
這種控制使得匹配規則變得簡單。不須要去檢查每一處聲明:咱們能夠爲元素從映射中取出相關的規則。這優化排除了 95% 的規則,因此在匹配過程當中甚至能夠不須要考慮(4.1)。
以以下樣式規則爲例:
p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}
複製代碼
第一條規則將被插入到類映射中。第二條規則插入 id 映射,而後第三條插入標籤映射。
參考下列 HTML 片斷:
<p class="error">an error occurred </p>
<div id=" messageDiv">this is a message</div>
複製代碼
咱們首先嚐試爲 p 元素找到規則。類表中有一個 「error」 鍵,在下面會找到 「p.error」 的規則。div 元素在 id 表和標籤表中找到相關規則。剩下的工做只是找出哪些根據鍵提取的規則是真正的匹配了。
好比 div 規則以下:
table div {margin: 5px}
複製代碼
它會從類表中提取,由於鍵是最右邊的選擇器,可是它不會匹配咱們的 div 元素,它沒有 table 祖先。
WebKit 和 火狐都作了這種操做。
樣式對象有對應每個可視化屬性(全部的 CSS 屬性但更通用)的屬性。若是一個屬性沒有被任何匹配的規則定義,這些屬性能夠經過父級元素樣式對象來繼承。其餘屬性使用默認值。
當有更多的定義時問題就來了——這時候須要層疊順序來解決這個問題。
樣式屬性的聲明可能會出如今多個樣式表中,或者在一個樣式表中聲明數次。這意味着應用工做的順序是很重要的。這叫作 「層疊」 順序。根據 CSS2 定義,層疊順序是(從低到高):
瀏覽器聲明是不重要的,當用戶只有把聲明標記爲 important 時纔會覆蓋創做者的內容。一樣順序的聲明會根據定義來排序,而後在根據指定的順序。HTML 可視屬性被轉換成匹配 CSS 聲明。它們被當作低權限的創做者規則。
選擇器的明確性經過以下 CSS2 規範來定義:
鏈接這四個數字 a-b-c-d(在大基數進制的數字系統),獲得明確性。
基於你須要使用的進製取決於在某個類目中定義的最多的數量。
舉個例子,若是 a = 14 你須要使用 16 進制。不太可能的狀況是 a = 17 的時候你須要使用 17 進制。後一種場景更像是這樣的選擇器:html body div div p ... (17 個元素在選擇器中……不是頗有可能)
舉個例子:
* {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */
複製代碼
在匹配規則之後,根據層疊規則排序。WebKit 使用冒泡排序爲小型列表而後合併成一個大的。WebKit 爲規則覆寫 「>」 操做實現排序。
static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
int spec1 = r1.selector()->specificity();
int spec2 = r2.selector()->specificity();
return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
}
複製代碼
WebKit 使用一個標記,記錄全部最高層加樣式表(包括 @imports)是否加載完畢。若是在 attaching 中沒有徹底加載,使用佔位符而且在文檔中標記,而後一旦樣式表被夾在會從新計算。
當渲染被建立和添加到樹中後,它尚未位置和尺寸。計算這些值的過程叫作佈局和重繪(reflow)。
HTML 使用的流基於佈局模型,意味着在大多數狀況下一次計算就能夠獲得幾何值。流以後的元素通常不會影響流以前的元素的幾何性質,因此經過文檔佈局能夠從左只有,從上到下的執行。有個例外:好比,HTML 表格可能會要求屢次遍歷(3.5)
座標系統是相對於根框架的。使用上左位置的座標。
佈局是個遞歸過程。它在根節點渲染開始,也就是對應 HTML 文檔的 <html>
元素。佈局經過一些或者所有框架層級,爲每次要求的渲染計算幾何信息。
根渲染的位置是 0,0 而且它的尺寸是瀏覽器窗口的可見部分的視窗。
全部的渲染都有 「layout」 和 「reflow」 方法,每一個渲染調用後代須要佈局的佈局方法。
爲了對每一次小改變不作充分的佈局,瀏覽器使用 「dirty bit」 系統。改變或者給它本身和後代添加標記的渲染視爲 「dirty」:須要佈局。
有兩種標記:「dirty」 和 「children are dirty」, 這意味着儘管渲染本身是合適的,它至少有一個子代須要佈局。
佈局在整個渲染樹上能被觸發——這是「全局」佈局。它能做爲如下結果發生:
佈局是增量的,只有髒渲染會佈局(這回引發一些額外佈局的損失)。
增量佈局當渲染是髒的時候觸發(異步的)。舉個例子,在從網絡的額外內容被添加到 DOM 樹後新的渲染將被添加到渲染樹。
增量佈局是經過異步完成的。火狐爲增量佈局將 「迴流命令」排隊,同時調度者會觸發這些命令批量執行,而後「髒」渲染被佈局。
腳本請求了樣式順序,像是「offsetHeight」能夠同步地觸發增量佈局。
全局佈局一般被同步觸發。
有時由於一些屬性,好比滾動位置改變,佈局會在初始化佈局以後做爲回調觸發。
優化當佈局被 「resize」 觸發時,或者渲染位置改變(不是尺寸),渲染尺寸從緩存中獲取而且不會從新計算。
在一些例子中只有一個子樹被修改,而且佈局沒有從根節點開始的話。這地方只會發生本地改變不會影響它的周圍——好比文本被插入進文本域(不然每次敲擊將會從根節點觸發佈局)。
佈局一般有如下默認:
火狐使用一個 「state」 對象(nxHTMLReflowState)做爲佈局參數(記爲「reflow」)。在它們之間這個狀態包括父級寬度。火狐的佈局輸出是 「metrics」 對象(nsHTMLReflowMetrics)。它將包含渲染計算的高度。
渲染器的寬度使用包含塊的寬度來計算,渲染樣式 「width」 屬性是 margin 和 border。
好比下面這個 div 的寬度:
<div style="width: 30%"/>
複製代碼
經過 WebKit 計算可能以下(RenderBox 類 的 calcWidth 方法):
包含的寬度是包含塊可用寬度的最大值或 0.這個能夠用寬度在例子中是這樣被計算的內容寬度:
clientWidth() - paddingLeft() - paddingRight()
複製代碼
客戶端寬度和客戶端高度表明一個包括邊距和滾動調的對象的內部
元素寬度是樣式屬性 「width」。它能夠計算成絕對值,經過計算容器寬度的百分比。
水平邊框和補白被添加。
目前這種這是「完美寬度」的計算。如今最小和最大寬度被計算。
若是最佳寬度比最大寬度更大,這個最大寬度將被使用。若是小於最小寬度(最小的不可破壞的單位)那麼最小寬度被使用。
只被緩存以防佈局使用,可是寬度不會改變。
當渲染到佈局的中間決定它須要換行時,渲染中止而後擴散須要換行佈局的父級。父級建立額外的渲染,而後在上面調用渲染。
在繪製階段,渲染被傳遞,而且渲染的 「paint()」 方法被調用用於在屏幕上展現內容。繪製使用 UI 基礎組件。
如同佈局,繪製也是全局的——整棵樹被繪製——或者增長。在增長繪製中,一些渲染在不影響整顆樹的狀況下改變。這個改變的渲染在屏幕上使它的矩形失效。這是由於操做系統把它當作一塊「髒區域」,同時生成了「繪製」事件。操做系統聰明的合併幾個區域變成一個。在 Chrome 中,這比較複雜由於渲染在主過程當中有不一樣的過程。Chrome 某些程度模擬了操做系統的行爲。表現層監聽了事件而且代理渲染根部的消息。樹被傳遞直到相關渲染到達。它將重繪本身(和一般它的子代)。
CSS2 定義了繪製過程的順序。實際上這個順序是元素在內容棧的存儲的地方。由於棧渲染從後向前,因此這個順序影響繪製。一個塊的棧順序渲染是:
火狐遍歷渲染樹,而後爲繪製矩形構建展現列表。它包含渲染層相關的矩形,在正確的繪製順序下(渲染層的背景和邊框等等)。這種方式樹爲一次重繪只會傳遞一次而不是數次——繪製全部的背景和圖片,而後是邊框等等。
火狐經過不添加將被隱藏的元素優化過程,像是元素徹底在其餘不透明元素下方。
在重繪前,WebKit 儲存舊的矩形做爲位圖。這樣只會渲染在新舊矩形之間的變化。
在響應變化時,瀏覽器嘗試最小化的可能的行爲。因此改變一個元素的顏色只會引發元素重繪。改變元素的位置會引發元素和它的子代或可能的兄弟節點的佈局和重繪。添加一個節點將引發節點的佈局和重繪。主要的變化,像是增長 「html」 元素的字號,將會引發緩存失效,整個樹的重佈局和重繪製。
渲染引擎是單線程的。幾乎全部的事,除了網絡操做,都發生在單線程中。在火狐和 Safari 中這是瀏覽器的主線程。在 Chrome 中,tab 過程是主線程。
網絡操做能夠經過幾個平行線程執行。平行連接數是受限的(2-6 個連接)。
瀏覽器主要線程是時間循環。這是一個保持過程活動的無限循環。它等待事件(像是佈局和繪製事件)而後執行它們。下面是火狐代碼的主要事件循環:
while (!mExiting)
NS_ProcessNextEvent(thread);
複製代碼
根據 CSS2 定義,canvas 條款描述 「格式化結構渲染的空間」:是瀏覽器繪製內容的地方。canvas 對每一個空間的尺寸是無限的,可是瀏覽器基於視窗的尺寸選擇一個初始化的寬度。
根據 www.w3.org/TR/CSS2/zin…,canvas 是透明的,若是包含其餘內容,然而瀏覽器定義一個顏色若是它沒有定義的話。
CSS 盒模型描述了一個矩形盒子,它在文檔中爲元素生成,同時根據可視化格式模型佈局。
每一個盒子有一個內容面積(好比文本和圖片等等),同時可選有間距,邊框,和邊距面積。
每一個節點生成 0 到 n 和盒子。
全部的元素都有 「display」 屬性,來定義將被生成的盒子類型。好比:
block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.
複製代碼
默認是 inline,可是瀏覽器樣式表可能設置其餘默認。舉個例子:「div」 元素的默認展現是 「block」。
你能夠在這裏查看默認樣式表的例子:www.w3.org/TR/CSS2/sam…
有三種方案:
定位方案經過設置 「position」 屬性和 「float」 屬性。
在靜態定位中,沒有位置被定義,而且使用默認位置。在其餘方案中,創做者定義位置用:上下左右。
盒子佈局的方式取決於:
塊狀盒子:在瀏覽器窗口中有本身的矩形的一種盒子形式。
內聯盒子:沒有本身的塊,可是內部有內容塊。
塊是一個接一個的格式化垂直。內聯是格式化水平。
內聯盒子內部有行或者 「line boxes」。行至少與最高的盒子同樣高並且能夠比它更高,當盒子對齊在 「baseline」時——意味着底部元素部分對齊另外一個元素的底部。若是容器寬度不夠,內聯會換行。這個一般發生在段落中。
相對定位-像一般同樣定位,而後根據變化移動。
一個浮動盒子漂移到行的左側或者右側。這個有趣的特性會讓其餘盒子圍繞着它。
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
複製代碼
看起來像這樣:
佈局精肯定義忽略正常流。元素不參與正常流。這個尺寸相對於容器。在固定定位中,容器是視窗。
注意:固定盒子在文檔滾動的時候不會移動。
這個經過 CSS 的 z-index 屬性定義。它表明沿 「z軸」 的第三維度。
盒子被分割成「棧」(稱做棧內容)。每一個棧後面的元素將會先畫在元素頂部,更接近用戶。在重疊的例子中,最前的元素將會隱藏較前的元素。
棧根據 z-index 屬性排序。從本地棧中盒子有 ‘z-index’ 屬性。視窗在最外部棧。
好比:
<style type="text/css">
div {
position: absolute;
left: 2in;
top: 2in;
}
</style>
<p>
<div
style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
</div>
<div
style="z-index: 1;background-color:green;width: 2in; height: 2in;">
</div>
</p>
複製代碼
結果是:
儘管紅色 div 在構建上高於綠色的,它可能在常規流以前,它的 z-index 屬性 更高,因此在根盒子持有的棧中更向前。