摘要: 理解瀏覽器原理。css
一旦 CSS 被瀏覽器下載,CSS 解析器就會被打開來處理它遇到的任何 CSS。這能夠是單個文檔內的 CSS、<style>
標記內的 CSS,也能夠是 DOM 元素的style
屬性內嵌的 CSS。所 有 CSS 都根據語法規範進行解析和標記。解析完成後,就會生成有一個包含全部選擇器、屬性和屬性各自值的數據結構。html
例如,考慮如下 CSS:web
.fancy-button { background: green; border: 3px solid red; font-size: 1em; }
以上 CSS 片斷將生成以下數據結構,以便在後續的過程當中方便使用:算法
值得注意的一件事是,瀏覽器將 background
和 border
的簡寫還原成普通寫法,也就是一個一個屬性的聲明,由於簡單寫主要方便開發人員的編寫,但從這裏開始,瀏覽器只處理普通寫法。完解析成以後,瀏覽器引擎繼續構建 DOM 樹。小程序
既然咱們已經解析了現有內容中的全部樣式,接着就是對它們進行樣式計算了。咱們嘗試儘可能對全部值減小到一個標準化的計算值。當離開計算階段時,任何維度值都被縮減爲三個可能的輸出之一:auto
、百分比或像素值。爲了清晰起見,讓咱們看幾個例子,看 web 開發人員寫了什麼,以及計算後的結果:微信小程序
如今咱們已經計算了數據存儲中的全部值,是時候處理級聯了。瀏覽器
因爲 CSS 來源有多種,因此瀏覽器須要一種方法來肯定哪些樣式應該應用於給定的元素。爲此,瀏覽器使用一個名爲 特殊性(specificity) 的公式,它計算選擇器中使用的標記、類、id 和屬性選擇器的數值,以及 !important
聲明的數值。微信
經過內聯 style
屬性在元素上定義的樣式被賦予一個等級,該等級優先於 <style>
塊或外部樣式表中的任何樣式。若是 Web 開發人員使用 !important
某個值,則該值將賽過任何 CSS,不管其位置如何,除非還有 !important
內聯。數據結構
同一級別的個數,數量多的優先級高,假設一樣即比較下一級別的個數。至於各級別的優先級例如如下:svg
!important > 內聯 > ID > 類 > 標籤 | 僞類 | 屬性選擇 > 僞對象 > 通配符 > 繼承
選擇器的特殊性由選擇器自己的組件肯定,特殊性值表述爲 5 個部分,如:
0,0,1,0,1
(1)、對於選擇器中給定的各個 !important
屬性值,加 1,0,0,0,0 。
(2)、對於選擇器中給定的各個 ID 屬性值,加 0,0,1,0,0 。
(3)、對於選擇器中給定的各個類屬性值、屬性選擇器或僞類,加 0,0,0,1,0 。
(4)、對於選擇器中給定的各個元素和僞元素,加 0,0,0,0,1 。僞元素是否具備特殊性?在這方面 CSS2 有些自相矛盾,不過 CSS2.1 很清楚的指出,僞元素具備特殊性,並且特殊性爲 0,0,0,0,1,同元素特殊性相同。
(4)、結合符(+ > [] ^= $= 等等特殊符號)和通配符(*)對特殊性沒有任何貢獻,此外通配符的特殊性爲 0,0,0,0,0。全是 0 有什麼意義呢?固然有意義!子元素繼承祖先元素的樣式根本沒有特殊性,所以當出現這種狀況後,通配符選擇器定義的樣式聲明也要優先於子元素繼承來的樣式聲明。由於就算特殊性是 0,也比沒有特殊性可言要強。
爲了說明這一點,讓咱們說明一些選擇器及其計算後的權重數值:
而當優先級與多個 CSS 聲明中任意一個聲明的優先級相等的時候,CSS 中最後的那個聲明將會被應用到元素上。
在下面的示例中,div
將具備藍色背景。
div { background: red; } div { background: blue; }
如今 CSS 將生成如下數據結構,在本文中,咱們將繼續在此基礎上進行構建。
CSS 也有來源,但它們的用途不一樣:
CSS 信息能夠從各類來源提供,這些來源能夠是 用戶(user) 和 做者(author) 及 用戶代理/瀏覽器(user agent),優先級以下:
用戶樣式
瀏覽器還容許用戶設置網頁的樣式,例如,咱們用 IE 瀏覽網站的時候,均可以經過瀏覽器查看菜單下的樣式或者文字大小子菜單來設置網頁實際的顯示效果。
做者樣式
網頁建立者創建的樣式表,通常會 css 文件出現或者是在頁面頭部裏定義的 style,也就是網站源代碼的一部分。例如,你們看百度和谷歌的頁面就不同,這就是做者樣式不同的結果。
用戶代理/瀏覽器樣式
也就是瀏覽器自身設置用來顯示網站的樣式,不一樣的瀏覽器可能有不一樣的樣式表,例如 IE 和 Firefox 的就不同,因此你們分別使用這兩種瀏覽器訪問同一個網站的時候,看到實際效果可能就不一樣。
一般狀況下,做者樣式具備最高的重要性,其次是用戶樣式,最後纔是瀏覽器樣式,可是若是出現了 !important
標記的話,那麼規則會被改變,經過 !important
能夠提升某種樣式的重要性,讓它的優先級高於其餘沒有加該聲明的全部樣式。
讓咱們進一步擴展咱們的數據集,看看當用戶將瀏覽器的字體大小設置爲最小 2em
時會發生什麼:
當瀏覽器擁有一個完整的數據結構,包含來自全部源的全部聲明時,它將按照規範對它們進行排序。首先,它將按來源排序,而後按特性(specificity)排序,最後按文檔順序排序。
從上圖可知,類名爲 .fancy-button
優先級最高(表中越上面優先級越高)。例如,從上表中,人會注意到用戶的瀏覽器首選項設置優先 於 Web 開發人員的設置樣式。如今,瀏覽器找到與選擇器匹配的全部 DOM 元素,並將獲得的計算樣式掛載到匹配的元素,在本例中 div
爲類名爲 .fancy-button
:
若是您但願瞭解更多關於級聯的工做原理,請查看官方規範。
雖然到目前爲止咱們已經作了不少,但尚未完成。如今咱們須要更新 CSS 對象模型(CSSOM)。 CSSOM 位於document.stylesheets
中,咱們須要對其進行更新,以便讓它知道咱們目前爲止已經解析和計算的全部內容。
Web 開發人員可能在沒有意識到的狀況下使用這些信息。例如,當調用 getComputedStyle() 時,若是須要,運行上面指出的相同過程
如今咱們已經應用了一個具備樣式的 DOM 樹,而後開始構建一個用於可視化目的的樹了。這棵樹出如今全部現代引擎中,被稱爲盒子樹(box tree)。爲了構造這棵樹,咱們遍歷 DOM 樹並建立零個或多個 CSS 盒子,每一個盒子都有一個 margin
、border
、padding
和 content
。
在本節中,咱們將討論如下 CSS 佈局概念:
display
元素的值來調用。一些最多見的格式化上下文是塊(塊格式化上下文或BFC),flex,grid,table-cells 和 inline。其餘一些 CSS 也能夠強制使用新的格式化上下文,例如 position: absolute
,float
或使用 multi-colum
。請記住,在計算階段,維度值能夠是三個值之一:auto、百分數或像素。佈局的目的是在Box Tree中調整全部盒子的大小和位置,使它們爲繪製作好準備。
下面示例能夠更容易地理解Box Tree是如何構建的。爲了便於理解,這裏不顯示單獨的 CSS 框,只顯示主盒(principal box)。讓咱們看看一個基本的 「Hello world」 佈局使用如下代碼:
<body> <p>Hello world</p> <style> body { width: 50px; } </style> </body>
瀏覽器從 body 元素開始,生成它的主盒(principal box),它的寬度爲50px
,默認高度爲auto
。
如今移動到 p
標籤並生成其主盒(principal box),而且因爲 p
標籤默認有邊距(margin),這將影響正文的高度,以下所示:
如今瀏覽器移動到 「Hello world」 文本,這是 DOM 中的文本節點。所以,咱們在佈局中生成一個 行內盒(line box) 。請注意,文本溢出了正文,咱們將在下一步處理這個問題。
由於加上「world」長度後實際長度比較設置大而且咱們沒有設置 overflow
屬性,因此引擎會向其父級報告它在佈局文本時中止的位置。
因爲父級已收到其子級沒法完成全部內容佈局的指令,所以它會克隆包含全部樣式的 行內盒(line box),並傳遞該框的信息以完成佈局。
佈局完成後,瀏覽器會返回 box tree
,解析還沒有解決的全部基於 auto
或基於百分比的值。 在圖中,能夠看到正文和段落如今包含全部 「Hello world」,由於它的 height 設置爲 auto
。
如今讓佈局變得更復雜一點。咱們將使用一個普通佈局,其中有一個按鈕,內容爲 「Share It」,並將其浮動到一段文本的左側。浮動自己被認爲是**「shrink-to-fit」** 上下文。之因此將其稱爲「shrink-to-fit」,是由於若是尺寸是自動的,則該框將圍繞其內容進行收縮。
浮動盒子是與這種佈局類型匹配的盒子的一種類型,可是還有許多其餘的盒子,例如絕對定位盒子(包括 position: fixed
)和基於自動調整大小的表格單元格,以下代碼:
<article> <button>SHARE IT</button> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pellentesq </p> </article> <style> article { min-width: 400px; max-width: 800px; background: rgb(191, 191, 191); padding: 5px; } button { float: left; background: rgb(210, 32, 79); padding: 3px 10px; border: 2px solid black; margin: 5px; } p { margin: 0; } </style>
該過程開始時遵循與「Hello world」示例相同的模式,所以我將跳到咱們開始處理浮動按鈕的位置。
因爲浮動建立了一個新的塊格式化上下文(BFC),而且是一個 shrink-to-fit
上下文,所以瀏覽器執行一種稱爲內容度量的特定佈局類型。
在這種模式下,它看起來與其餘佈局相同,但有一個重要的區別,即它是在無限空間中完成的。在此階段,瀏覽器所作的就是以 BFC 的最大和最小寬度佈局 BFC 樹。
在本例中,它使用文本佈局一個按鈕,所以其最窄的大小(包括全部其餘 CSS 框)將是最長單詞的大小。在最寬的地方,它將是一行的全部文本,加上 CSS Box。注意:這裏按鈕的顏色不是文字的顏色。這只是爲了說明問題。
如今咱們知道最小寬度是 86px,最大寬度是 115px,咱們將此信息傳遞迴父類的 box,讓它決定寬度並適當地放置按鈕。在這個場景中,有足夠的空間來適應浮動的最大大小,這就是按鈕的佈局方式。
爲了確保瀏覽器遵循標準,而且內容圍繞浮動,瀏覽器更改了 article 的 BFC 的幾何形狀。這個幾何圖形被傳遞給段落,以便在段落佈局期間使用。
從這裏開始,瀏覽器遵循與第一個示例相同的佈局過程——可是它確保任何內聯內容的內聯和塊的起始位置都位於浮動所佔用的約束空間以外。
當瀏覽器繼續沿着樹向下移動並克隆節點時,它將越過約束空間的塊位置。這容許最後一行文本(以及它以前的一行)之內聯方向開始於 content box 的開頭。而後瀏覽器返回到樹中,根據須要解析 auto 和百分數。
關於佈局如何工做的最後一個方面是碎片化。 若是你曾經打印過網頁或使用過 CSS 多列,那麼你已經利用了碎片。 碎片化是將內容分開以使其適合不一樣幾何形狀的邏輯。 讓咱們來看看同一個例子,利用 CSS 多列狀況:
<body> <div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nibh orci, tincidunt eget enim et, pellentesque condimentum risus. Aenean sollicitudin risus velit, quis tempor leo malesuada vel. Donec consequat aliquet mauris. Vestibulum ante ipsum primis in faucibus </p> </div> <style> body { columns: 2; column-fill: auto; height: 300px; } </style> </body>
一旦瀏覽器到達 multicol 格式化上下文盒子,它就會看到它有一組設定的列。
它遵循之前相似的克隆模型,並建立了一個具備正確維度的碎片處理程序,以知足做者對其列的要求。
而後瀏覽器按照與以前相同的模式儘量多地佈局行,而後瀏覽器建立另外一個碎片管理器,並繼續完成佈局。
來回顧一下咱們如今的狀況,咱們取出全部的 CSS 內容,對其進行解析,將其級聯到 DOM 樹中,並完成佈局。可是咱們尚未對布圖應用顏色、邊框、陰影和相似的設計處理——處理這些過程被稱爲繪畫。
繪畫基本上是由 CSS 標準化的,簡單地說,你能夠按照如下順序繪畫:
更多繪畫的順序可查看 CSS 2.2 Appendix E。
所以,若是咱們從前面的「SHARE IT」按鈕開始,並遵循這個過程,它繪製過程大體以下:
完成後,它將轉換爲位圖,最終每一個佈局元素(甚至文本)都成爲引擎中的圖像。
如今,咱們大多數的網站都不是由單一的元素組成的。此外,咱們常常但願某些元素出如今其餘元素之上。爲了實現這一點,咱們能夠利用 z-index
的特性將一個元素疊加到另外一個元素上。
這可能感受就像咱們在設計軟件中使用圖層同樣,可是惟一存在的圖層是在瀏覽器的合成器中。看起來好像咱們在使用 z-index
建立新層,但實際上並非這樣,那麼究竟是怎麼樣呢?
咱們要作的是建立一個新的堆棧上下文。建立一個新的堆疊上下文能夠有效地改變你繪製元素的順序。讓咱們來看一個例子:
<body> <div id="one"> Item 1 </div> <div id="two"> Item 2 </div> <style> body { background: lightgray; } div { width: 300px; height: 300px; position: absolute; background: white; z-index: 2; } #two { background: green; z-index: 1; } </style> </body>
若是沒有使用 z-index
,上面的文檔將按照文檔順序繪製,這將把 「Item 2」 置於 「Item 1」 之上。但因爲 z-index
的影響,繪畫順序發生了變化。讓咱們逐步完成每一個階段,相似於咱們以前完成佈局的方式。
瀏覽器以根框開頭,咱們在後臺畫畫。
而後瀏覽器按照文檔順序遍歷較低層次的堆棧上下文(在本例中是「Item 2」),並開始按照上面的規則繪製該元素。
而後它遍歷到下一個最高的堆棧上下文(在本例中是「Item 1」),並按照 CSS 2.2 中定義的順序繪製它。
z-index
不影響顏色,隻影響哪些元素對用戶可見,所以也不影響哪些文本和顏色可見。
在這個階段,咱們至少有一個位圖從繪畫傳遞到合成。合成程序的工做是建立一個或多個層,並將位圖呈現到屏幕上供最終用戶查看。
此時一個合理的問題是,「爲何任何站點都須要不止一個位圖或合成層?」,根據咱們目前看到的例子,咱們真的不會這麼作。咱們來看一個稍微複雜一點的例子。假設在一個假設的世界中,Office 團隊想讓 Clippy 從新上線,他們想經過 CS S 轉換讓 Clippy 跳動來吸引人們對他的注意。
動畫 Clippy 的代碼能夠是這樣的:
<div class="clippy"></div> <style> .clippy { width: 100px; height: 100px; animation: pulse 1s infinite; background: url(clippy.svg); } @keyframes pulse { from { transform: scale(1, 1); } to { transform: scale(2, 2); } } </style>
當瀏覽器讀取 web 開發人員但願在無限循環中爲 Clippy 添加動畫時,它有兩個選項:
在大多數狀況下,瀏覽器將選擇選項 2 並生成如下內容(我有意簡化了 Word Online 爲此示例生成的圖層數量):
而後,它將從新組合剪輯位圖在正確的位置,並處理脈動動畫。這對於性能來講是一個很好的優點,由於在許多引擎中,合成程序是在它本身的線程上的,這樣就能夠解除主線程的阻塞。若是瀏覽器選擇上面的選項 1,它將不得不阻塞每一幀以完成相同的結果,這將對最終用戶的性能和響應能力產生負面影響。
正如咱們剛剛瞭解到的,咱們使用了全部的樣式和 DOM,並生成了一個呈現給最終用戶的圖像。那麼瀏覽器如何建立交互性的假象呢?嗯,我相信你如今已經學過了,因此讓咱們看一個例子,用咱們的 「SHARE IT」 按鈕做爲類比:
button { float: left; background: rgb(210, 32, 79); padding: 3px 10px; border: 2px solid black; } button:hover { background: teal; color: black; }
咱們在這裏添加的是一個僞類,它告訴瀏覽器在用戶懸停在按鈕上時更改按鈕的背景和文本顏色。這就引出了一個問題,瀏覽器如何處理這個問題?
瀏覽器不斷跟蹤各類輸入,當這些輸入正在移動時,它會經歷稱爲命中測試的過程。 對於此示例,該過程以下所示:
:hover
在聲明塊內部有一個僅使用繪製樣式調整的僞類。但願這部分對你關於 css 解析過程多多少少有點幫助,共進步!
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟件、百姓網等衆多品牌企業。歡迎你們免費試用!