推薦理由:文章由淺入深,獲益匪淺。css
提起 CSS 不少童鞋都很不屑,尤爲是看到 RedMonk 2019 Programming Language Rankings 的時候,CSS 居然排到了第七位。html
咱們先來看看這張排行榜:node
既然 CSS 這麼重要,那麼咱們花點時間來研究相關原理也就物有所值了。
本節咱們就來講說 CSS 渲染以及優化相關的內容,主要圍繞如下幾點,由淺入深,瞭解前因後果:git
用戶界面,包括瀏覽器中可見的地址輸入框、瀏覽器前進返回按鈕、書籤,歷史記錄等用戶可操做的功能選項。github
瀏覽器引擎,能夠在用戶界面和渲染引擎之間傳送指令或在客戶端本地緩存中讀寫數據,是瀏覽器各個部分之間相互通訊的核心。web
渲染引擎,解析 DOM 文檔和 CSS 規則並將內容排版到瀏覽器中顯示有樣式的界面,也就是排版引擎,咱們常說的瀏覽器內核主要指的就是渲染引擎。正則表達式
網絡功能模塊,是瀏覽器開啓網絡線程發送請求以及下載資源的模塊。算法
JS 引擎,解釋和執行 JS 腳本部分,例如 V8 引擎。後端
UI 後端則是用於繪製基本的瀏覽器窗口內控件,好比組合選擇框、按鈕、輸入框等。瀏覽器
數據持久化存儲,涉及 Cookie、LocalStorage 等一些客戶端存儲技術,能夠經過瀏覽器引擎提供的 API 進行調用。
渲染引擎,解析 DOM
文檔和 CSS
規則並將內容排版到瀏覽器中顯示有樣式的界面,也就是排版引擎,咱們常說的瀏覽器內核主要指的就是渲染引擎。
上圖中,咱們須要關注兩條主線:
HTML Parser
生成的 DOM
樹;CSS Parser
生成的 Style Rules(CSSOM 樹)
;在這以後,DOM
樹與 Style Rules
會生成一個新的對象,也就是咱們常說的 Render Tree
渲染樹,結合 Layout
繪製在屏幕上,從而展示出來。
1.優先級
選擇器 | 權重 |
---|---|
!important | 1/0(無窮大) |
內聯樣式 | 1000 |
ID | 100 |
類/僞類/屬性 | 10 |
元素/僞元素 | 1 |
通配符/子選擇器/相鄰選擇器 | 0 |
!important > 行內樣式(權重1000) > ID 選擇器(權重 100) > 類選擇器(權重 10) > 標籤(權重1) > 通配符 > 繼承 > 瀏覽器默認屬性
<div > <p id="box" class="text">Jartto's blog</p> </div> <style> #box{color: red;} .text{color: yellow;} </style>
猜一猜,文本會顯示什麼顏色?當你知道 「ID 選擇器 > 類選擇器 」的時候,答案不言自明。
<div id="box"> <p class="text">Jartto's blog</p> </div> <style> #box{color: red;} .text{color: blue;} </style>
這裏就考查到了規則「類選擇器 > 繼承」,ID
對文原本說是繼承過來的屬性,因此優先級不如直接做用在元素上面的類選擇器。
2.繼承性
有哪些屬性是能夠繼承的呢,咱們簡單分一下類:
font-family
、font-size
、font-weight
等 f
開頭的 CSS 樣式。text-align
、text-indent
等 t
開頭的樣式。color
。詳細的規則,請看下圖:
<div> <ol> <li> Jartto's blog </li> </ol> </div> <style> div { color : red!important; } ol { color : green; } </style>
增長了 !important
,猜一猜,文本顯示什麼顏色?
3.層疊性
層疊就是瀏覽器對多個樣式來源進行疊加,最終肯定結果的過程。
CSS
之因此有「層疊」的概念,是由於有多個樣式來源。
CSS
層疊性是指 CSS
樣式在針對同一元素配置同一屬性時,依據層疊規則(權重)來處理衝突,選擇應用權重高的 CSS
選擇器所指定的屬性,通常也被描述爲權重高的覆蓋權重低的,所以也稱做層疊。
<div > <p class="two one">Jartto's blog</p> </div> <style> .one{color: red;} .two{color: blue;} <style>
若是兩個類選擇器同時做用呢,究竟以誰爲準?這裏咱們要考慮樣式表中兩個類選擇器的前後順序,後面的會覆蓋前面的,因此文本固然顯示藍色了。
<div> <div> <div>Jartto's blog</div> </div> </div> <style> div div div { color: green; } div div { color: red; } div { color: yellow; } <style>
這個比較直接,算一下權重,誰大聽誰的。
<div id="box1" class="one"> <div id="box2" class="two"> <div id="box3" class="three"> Jartto's blog </div> </div> </div> <style> .one .two div { color : red; } div #box3 { color : yellow; } #box1 div { color : blue; } </style>
權重:
0 0 2 1 0 1 0 1 0 1 0 1
<div id="box1" class="one"> <div id="box2" class="two"> <div id="box3" class="three"> Jartto's blog </div> </div> </div> <style> .one .two div { color : red; } #box1 div { color : blue; } div .three { color : green; } </style>
權重:
0 0 2 1 0 1 0 1 0 0 1 1
若是你對上面這些問題都瞭如指掌,那麼恭喜你,基礎部分順利過關,能夠繼續升級了!
1.咱們來把 CSS
拎出來看一下,HTML Parser
會生成 DOM
樹,而 CSS Parser
會將解析結果附加到 DOM
樹上,以下圖:
2.CSS
有本身的規則,通常以下:WebKit
使用 Flex
和 Bison
解析器生成器,經過 CSS
語法文件自動建立解析器。Bison
會建立自下而上的移位歸約解析器。Firefox
使用的是人工編寫的自上而下的解析器。
這兩種解析器都會將 CSS
文件解析成 StyleSheet
對象,且每一個對象都包含 CSS
規則。CSS
規則對象則包含選擇器和聲明對象,以及其餘與 CSS
語法對應的對象。
3.CSS
解析過程會按照 Rule
,Declaration
來操做:
4.那麼他是如何解析的呢,咱們不妨打印一下 CSS Rules
:
控制檯輸入:
document.styleSheets[0].cssRules
打印出來的結果大體分爲幾類:
規則貌似有點看不懂,不用着急,咱們接着往下看。
5.CSS
解析和 Webkit
有什麼關係?
CSS 依賴 WebCore 來解析,而 WebCore 又是 Webkit 很是重要的一個模塊。
要了解 WebCore 是如何解析的,咱們須要查看相關源碼:
CSSRule* CSSParser::createStyleRule(CSSSelector* selector) { CSSStyleRule* rule = 0; if (selector) { rule = new CSSStyleRule(styleElement); m_parsedStyleObjects.append(rule); rule->setSelector(sinkFloatingSelector(selector)); rule->setDeclaration(new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedProperties)); } clearProperties(); return rule; }
從該函數的實現能夠很清楚的看到,解析器達到某條件須要建立一個 CSSStyleRule
的時候將調用該函數,該函數的功能是建立一個 CSSStyleRule
,並將其添加已解析的樣式對象列表 m_parsedStyleObjects
中去,這裏的對象就是指的 Rule
。
注意:源碼是爲了參考理解,不須要逐行閱讀!
Webkit
使用了自動代碼生成工具生成了相應的代碼,也就是說詞法分析和語法分析這部分代碼是自動生成的,而 Webkit
中實現的 CallBack
函數就是在 CSSParser
中。
這時候就不得不提到 AST
了,咱們繼續剖析。
補充閱讀:Webkit 對 CSS 支持
6.關於 AST
若是對 AST 還不瞭解,請移步 AST 抽象語法樹。這裏咱們不作過多解釋,主要圍繞如何解析這一過程展開,先來看一張 Babel
轉換過程圖:
咱們來舉一個簡單的例子,聲明一個箭頭函數,以下:
let jarttoTest = () => { // Todo }
經過在線編譯,生成以下結果:
從上圖咱們能夠看出:咱們的箭頭函數被解析成了一段標準代碼,包含了類型,起始位置,結束位置,變量聲明的類型,變量名,函數名,箭頭函數表達式等等。
標準的解析代碼,咱們能夠對其進行一些加工和處理,以後經過相應 API 輸出。
不少場景都會用到這個過程,如:
場景千千萬,可是都離不開一個過程,那就是:
AST 轉換過程:解析 - 轉換 - 生成
到這裏,CSS
如何解析的前因後果咱們已經很是清楚了,能夠回到文章開頭的那個流程圖了,相信你必定會有另外一翻感悟。
渲染引擎解析 CSS
選擇器時是從右往左解析,這是爲何呢?舉個例子:
<div> <div class="jartto"> <p><span> 111 </span></p> <p><span> 222 </span></p> <p><span> 333 </span></p> <p><span class='yellow'> 444 </span></p> </div> </div> <style> div > div.jartto p span.yellow { color: yellow; } </style>
咱們按照「從左到右」的方式進行分析:
div
節點。div
節點內找到全部的子 div
,而且是 class = 「jartto」
。p span.yellow
等狀況。div
或者 p
節點,而後去搜索下個節點,重複這樣的過程。這樣的搜索過程對於一個只是匹配不多節點的選擇器來講,效率是極低的,由於咱們花費了大量的時間在回溯匹配不符合規則的節點。
咱們按照「從右向左」的方式進行分析:
class=「yellow」
的 span
元素。p
元素,若是不是則進入同級其餘節點的遍歷,若是是則繼續匹配父節點知足 class=「jartto」
的 div
容器。綜上所述,咱們能夠得出結論:
瀏覽器 CSS 匹配核心算法的規則是以從右向左方式匹配節點的。
這樣作是爲了減小無效匹配次數,從而匹配快、性能更優。
因此,咱們在書寫 CSS Selector
時,從右向左的 Selector Term
匹配節點越少越好。
不一樣 CSS
解析器對 CSS Rules
解析速度差別也很大,感興趣的童鞋能夠看看 CSS 解析引擎,這裏再也不贅述。
瀏覽器還有一個很是棒的策略,在特定狀況下,瀏覽器會共享 Computed Style
,網頁中能共享的標籤很是多,因此能極大的提高執行效率!
若是能共享,那就不須要執行匹配算法了,執行效率天然很是高。
若是兩個或多個 Element
的 ComputedStyle
不經過計算能夠確認他們相等,那麼這些 ComputedStyle
相等的 Elements
只會計算一次樣式,其他的僅僅共享該 ComputedStyle
。
<section class="one"> <p class="desc">One</p> </section> <section class="one"> <p class="desc">two</p> </section>
如何高效共享 Computed Style ?
TagName
和 Class
屬性必須同樣。Style
屬性。哪怕 Style
屬性相等,他們也不共享。3.不能使用 Sibling selector
,譬如: first-child
、 :last-selector
、 + selector
。
4.mappedAttribute
必須相等。
爲了更好的說明,咱們再舉兩個例子:
不能共享,上述規則 2
:
<p style="color:red">jartto's</p> <p style="color:red">blog</p>
能夠共享,上述規則 4
:
<p align="middle">jartto's</p> <p align="middle">blog</p>
到這裏,相信你對 ComputedStyle
有了更多的認識,代碼也就更加精煉和高效了。
須要注意的是:瀏覽器並非一獲取到 CSS
樣式就立馬開始解析,而是根據 CSS
樣式的書寫順序將之按照 DOM 樹的結構分佈渲染樣式,而後開始遍歷每一個樹結點的 CSS
樣式進行解析,此時的 CSS
樣式的遍歷順序徹底是按照以前的書寫順序。
在解析過程當中,一旦瀏覽器發現某個元素的定位變化影響佈局,則須要倒回去從新渲染。
咱們來看看下面這個代碼片斷:
width: 150px; height: 150px; font-size: 24px; position: absolute;
當瀏覽器解析到 position
的時候忽然發現該元素是絕對定位元素須要脫離文檔流,而以前倒是按照普通元素進行解析的,因此不得不從新渲染。
渲染引擎首先解除該元素在文檔中所佔位置,這就致使了該元素的佔位狀況發生了變化,其餘元素可能會受到它迴流的影響而從新排位。
咱們對代碼進行調整:
position: absolute; width: 150px; height: 150px; font-size: 24px;
這樣就能讓渲染引擎更高效的工做,但是問題來了:
在實際開發過程當中,咱們如何能保證本身的書寫順序是最優呢?
這裏有一個規範,建議順序大體以下:
定位屬性
position display float left top right bottom overflow clear z-index
自身屬性
width height padding border margin background
文字樣式
font-family font-size font-style font-weight font-varient color
文本屬性
text-align vertical-align text-wrap text-transform text-indent text-decoration letter-spacing word-spacing white-space text-overflow
CSS3 中新增屬性
content box-shadow border-radius transform
固然,咱們須要知道這個規則就夠了,剩下的能夠交給一些插件去作,譬如 CSSLint(能用代碼實現的,千萬不要去浪費人力)。
咱們從瀏覽器構成,聊到了渲染引擎,再到 CSS
的解析原理,最後到執行順序,作了一系列的探索。指望你們能從 CSS
的渲染原理中瞭解整個過程,從而寫出更高效的代碼。
id selector
很是的高效在使用 id selector
的時候須要注意一點:由於 id
是惟一的,因此不須要既指定 id
又指定 tagName
:
/* Bad */ p#id1 {color:red;} /* Good */ #id1 {color:red;}
node
譬如:
/* Bad */ div > div > div > p {color:red;} /* Good */ p-class{color:red;}
attribute selector
如:p[att1=」val1」]
,這樣的匹配很是慢。更不要這樣寫:p[id="id1"]
,這樣將 id selector
退化成 attribute selector
。
/* Bad */ p[id="jartto"]{color:red;} p[class="blog"]{color:red;} /* Good */ #jartto{color:red;} .blog{color:red;}
相似:
.foo { -moz-border-radius: 5px; border-radius: 5px; }
能夠參考這個 Css 規範。
font-faces 不能使用超過5個web字體 import 禁止使用@import regex-selectors 禁止使用屬性選擇器中的正則表達式選擇器 universal-selector 禁止使用通用選擇器* unqualified-attributes 禁止使用不規範的屬性選擇器 zero-units 0後面不要加單位 overqualified-elements 使用相鄰選擇器時,不要使用沒必要要的選擇器 shorthand 簡寫樣式屬性 duplicate-background-images 相同的url在樣式表中不超過一次
CSS
文檔體積CSS
規則(Remove empty rules
)。0
不須要單位。0.xx
,能夠省略小數點以前的 0
。h1-h6
元素定義過多的樣式。CSS Will Change
WillChange
屬性,容許做者提早告知瀏覽器的默認樣式,使用一個專用的屬性來通知瀏覽器留意接下來的變化,從而優化和分配內存。
@import
使用 @import
引入 CSS
會影響瀏覽器的並行下載。
使用 @import
引用的 CSS
文件只有在引用它的那個 CSS
文件被下載、解析以後,瀏覽器纔會知道還有另一個 CSS
須要下載,這時纔去下載,而後下載後開始解析、構建 Render Tree
等一系列操做。
多個 @import
會致使下載順序紊亂。在 IE 中,@import
會引起資源文件的下載順序被打亂,即排列在 @import
後面的 JS
文件先於 @import
下載,而且打亂甚至破壞 @import
自身的並行下載。
Reflow
)瀏覽器從新計算佈局位置與大小。
常見的重排元素:
width height padding margin display border-width border top position font-size float text-align overflow-y font-weight overflow left font-family line-height vertical-align right clear white-space bottom min-height
computedStyle
ChildSelector
。更多請查看上文 - 高效的 ComputedStyle
當頁面發生重繪時,它們會下降瀏覽器的渲染性能。因此在編寫 CSS
時,咱們應該儘可能減小使用昂貴屬性,如:
box-shadow
。border-radius
。filter
。:nth-child
。若是某些屬性能夠繼承,那麼天然沒有必要在寫一遍。
CSS
順序規則上面就是對本文的一個總結,你瞭解 CSS
具體的實現原理,曉得規避錯誤書寫方式,知道爲何這麼優化,這就夠了。
性能優化,進無止境。
文章首發於 Jartto's blog