在前端工程師的日常中,使用元素選取器是稀鬆平常的事情;無論你是撰寫一般的 CSS,或是需要過編譯的 SASS、SCSS、LESS,最終都仍會被編譯成一行一行的 CSS 樣式屬性,交給瀏覽器解析、套用;但你有沒有想過這件事情是如何實現的呢?
本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!
我們先複習一下前幾天提過的瀏覽器渲染步驟:
image by faressoft
我們撰寫的 CSS 在瀏覽器載入後,會被解析成 CSSOM Tree,並嘗試與 Dom 疊加成 Render Tree,隨後進行計算位置、渲染等步驟;這樣看來, CSS 屬性套用的關鍵就在於如何從 CSS 轉化成 CSSOM Tree,以及怎麼把 CSSOM 套用到 DOM 上去了。
別急,讓我們繼續看下去。
當我們寫下一組 CSS 樣式例如:
#id .class h4 + p {
...
}
當瀏覽器解析它時,你可能會預期 CSS 會被由左到右的依序找出 #id
> .class
> h4
> p
,最後套用,但實際上瀏覽器解析 CSS 的順序是由右到左的 p
> h4
> .class
> #id
。
很反直覺對吧?但如果考慮到效能議題,由右到左的解析會比由左到右優秀不少喔!
考慮以下的例子,假設這有這樣的 HTML:
<div id="div1">
<div class="a">
<div class="b">
...
</div>
<div class="c">
<div class="d">
...
</div>
<div class="e">
...
</div>
</div>
</div>
<div class="f">
<div class="c">
<div class="d">
...
</div>
</div>
</div>
</div>
以及這邊五條 CSS 樣式規則:
#div1 .c .d {}
.f .c .d {}
.a .c .e {}
#div1 .f {}
.c .d {}
讓我們模擬一下,如果把 CSS 從左到右解析,將會生成類似這樣的 CSSOM Tree:
透過 <div class="d">
中的 .d
來思考,這樣的 CSSOM Tree 在套用樣式時,必須對 所有 的樣式規則進行檢查,確認樣式規則是否影響到 .d
,最後才能確定可能會影響到 .d
的樣式規則有這三條:
#div1 .c .d
.f .c .d
.c .d
以此類推,每個 DOM Tree 上的元素,都必須遍盡所有的樣式規則,才可以取得個別的樣式,這樣會造成巨量冗餘的計算,進而危害效能。
反過來,如果將前述的 CSS 由右到左進行解析, CSSOM Tree 則可能會如下:
如同先前的範例,我們從 <div class="d">
中 .d
的角度來看看,由於會被樣式規則影響到的目標元素,已經全都集合在第一層了,我們不用再去遍盡整個 CSSOM Tree,而是只需要檢查 .d
以下的子屬性節點是否符合實際 DOM 結構,再將所有符合的樣式規則擷取出來,便能完成 .d
對此元素的樣式規則套用。
由右到左的解析順序,能夠將所有共用的規則路徑收攏在一起,瀏覽器進行屬性比對時無須再遍盡整顆 CSSOM Tree,減少了大量無效的比對計算。
也可以換個方式思考:在HTML 的結構中,一個元素可以有無數個子元素,但只能有一個父元素,由子找父(由下往上)搜尋絕對是比較快的。
將 CSSOM Tree 解析出來之後,就能夠和 DOM Tree 結合套用了嗎?事情有這麼簡單就好啦~
除了開發者定義好的 CSS 檔外,還有幾個地方可能會定義樣式規則,影響畫面的呈現:
瀏覽器負責處理 CSS 的部分,會將前述的所有東西及 CSS 檔定義的樣式規則分別整理成個別的樣式規則組(CSS Rule Set),內容紀載了樣式規則、目標屬性等資訊。
為了提升後面計算的效能,瀏覽器的 CSS 核心會依照樣式規則組中個別規則的目標屬性,將其分組存放;一共分成以下四組
這樣在取用時,可以依照目標元素是否有這個屬性,快速篩出可能會套用的樣式。
最後就是套用規則啦~瀏覽器會依照下列順序及樣式規則權重套用所有樣式規則:
最後兩項有沒有很眼熟?這就是 CSS 樣式權重中,inline style 及 !important 輾壓樣式規則群雄背後的原因!
可能會有讀者好奇:為什麼 inline style 不同於開發者定義的 CSS,會被另外處理?
我們可以回顧一下瀏覽器渲染的步驟,由於 inline style 是存在於 DOM 元素中,只有在要將 CSS 套用到 DOM 上時才會接觸到,無法事前將兩者結合計算。
快速看過了 CSS 選取器從解析到套用的整個過程,想想日常生活中所使用的各大網站,背後都有這種龐大繁複的運算機制,是不是覺得很恐怖啊XD
如同昨天的渲染過程,實際上瀏覽器在這一段也做了優化機制;瀏覽器會自動將狀態一樣的元素做樣式快取。什麼是狀態一樣呢?要滿足以下幾個條件:
~
、+
、:first-child
...)由於上面的條件,以及前面討論到的 CSS 運算過程,撰寫 CSS 時也有幾個地方可以稍微留心一下:
若能注意到諸如此類的小細節,CSS 效能自然也就能大幅提升。
認識了 CSS 選取器之後,一定有讀者好奇,JavaScript 的元素選取器又是怎麼一回事呢?可以參考 jQuery 的 Source Code,是由左到右的解析,至於為什麼會不一樣呢,其實在文中也有答案,就留給讀者您思考挖掘囉~
三天的 CSS 深度之旅告一段落,今天的主題真的有點艱難,其實寫的時候筆者自己也有許多不太確定的地方,但相信文章 PO 出來拋磚引玉後,就會有更多討論了(吧?)。明天就要進入重頭戲 JavaScript 啦~如果您對文章中的資訊有任何疑問或不清楚的地方,都歡迎您隨時於下方留言區回覆討論;一天一題我們明天見~
筆者
Gary
半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。相信一切安排都是最好的路。
"反過來,如果將前述的 CSS 由右到左進行解析, CSSOM Tree 則可能會如下"之後的圖怪怪的,.f .c .d好像不見了