作為前端,我們每天都在與css打交道,那么css的原理是什么呢?
一、瀏覽器渲染
開篇,我們還是不厭其煩的回顧一下瀏覽器的渲染過程,先上圖:
正如上圖所展示的,我們瀏覽器渲染過程分為了兩條主線:
其一,html Parser 生成的 DOM 樹;
其二,CSS Parser 生成的 Style Rules ;
在這之后,DOM 樹與 Style Rules 會生成一個新的對象,也就是我們常說的 Render Tree 渲染樹,結合 Layout 繪制在屏幕上,從而展現出來。
二、Webkit CSS 解析器
瀏覽器 CSS 模塊負責 CSS 腳本解析,並為每個 Element 計算出樣式。CSS 模塊雖小,但是計算量大,設計不好往往成為瀏覽器性能的瓶頸。
CSS 模塊在實現上有幾個特點:CSS 對象眾多(顆粒小而多),計算頻繁(為每個 Element 計算樣式)。這些特性決定了 webkit 在實現 CSS 引擎上采取的設計,算法。如何高效的計算樣式是瀏覽器內核的重點也是難點。
Webkit 使用了自動代碼生成工具生成了相應的代碼,也就是說詞法分析和語法分析這部分代碼是自動生成的,而 Webkit 中實現的 CallBack 函數就是在 CSSParser 中。
CSS 的一些解析功能的入口也在此處,它們會調用 lex , parse 等生成代碼。相對的,生成代碼中需要的 CallBack 也需要在這里實現。
舉例來說,現在我們來看其中一個回調函數的實現,createStyleRule(),該函數將在一般性的規則需要被建立的時候調用。
解析器達到某條件需要創建一個 CSSStyleRule 的時候將調用該函數,該函數的功能是創建一個 CSSStyleRule ,並將其添加已解析的樣式對象列表 m_parsedStyleObjects 中去,這里的對象就是指的 Rule 。
那么如此一來,經過這樣一番解析后,作為輸入的樣式表中的所有 Style Rule 將被轉化為 Webkit 的內部模型對象 CSSStyleRule 對象,存儲在 m_parsedStyleObjects 中,它是一個 Vector。
但是我們解析所要的結果是什么?
1.通過調用 CSSStyleSheet 的 parseString 函數,將上述 CSS 解析過程啟動,解析完一遍后,把 Rule 都存儲在對應的 CSSStyleSheet 對象中;
2.由於目前規則依然是不易於處理的,還需要將之轉換成 CSSRuleSet。也就是將所有的純樣式規則存儲在對應的集合當中,這種集合的抽象就是 CSSRuleSet;
3.CSSRuleSet 提供了一個 addRulesFromSheet 方法,能將 CSSStyleSheet 中的 rule 轉換為 CSSRuleSet 中的 rule ;
4.基於這些個 CSSRuleSet 來決定每個頁面中的元素的樣式;
三、CSS 選擇器解析順序
可能很多同學都知道排版引擎解析 CSS 選擇器時是從右往左解析,這是為什么呢?
1.html 經過解析生成 DOM Tree(這個我們比較熟悉);而在 CSS 解析完畢后,需要將解析的結果與 DOM Tree 的內容一起進行分析建立一棵 Render Tree,最終用來進行繪圖。Render Tree 中的元素(WebKit 中稱為「renderers」,Firefox 下為「frames」)與 DOM 元素相對應,但非一一對應:一個 DOM 元素可能會對應多個 renderer,如文本折行后,不同的「行」會成為 render tree 種不同的 renderer。也有的 DOM 元素被 Render Tree 完全無視,比如 display:none 的元素。
2.在建立 Render Tree 時(WebKit 中的「Attachment」過程),瀏覽器就要為每個 DOM Tree 中的元素根據 CSS 的解析結果(Style Rules)來確定生成怎樣的 renderer。對於每個 DOM 元素,必須在所有 Style Rules 中找到符合的 selector 並將對應的規則進行合並。選擇器的「解析」實際是在這里執行的,在遍歷 DOM Tree 時,從 Style Rules 中去尋找對應的 selector。
3.因為所有樣式規則可能數量很大,而且絕大多數不會匹配到當前的 DOM 元素(因為數量很大所以一般會建立規則索引樹),所以有一個快速的方法來判斷「這個 selector 不匹配當前元素」就是極其重要的。
4.如果正向解析,例如「div div p em」,我們首先就要檢查當前元素到 html 的整條路徑,找到最上層的 div,再往下找,如果遇到不匹配就必須回到最上層那個 div,往下再去匹配選擇器中的第一個 div,回溯若干次才能確定匹配與否,效率很低。
如果換個思路,我們一開始過濾出跟目標節點最符合的集合出來,再在這個集合進行搜索,大大降低了搜索空間。來看看從右到左來解析選擇器:
1.首先就查找到 的元素;
2.緊接着我們判斷這些節點中的前兄弟節點是否符合 P 這個規則,這樣就又減少了集合的元素,只有符合當前的子規則才會匹配再上一條子規則。
試想一下,如果采用從左至右的方式讀取 CSS 規則,那么大多數規則讀到最后(最右)才會發現是不匹配的,這樣會做費時耗能,最后有很多都是無用的;而如果采取從右向左的方式,那么只要發現最右邊選擇器不匹配,就可以直接舍棄了,避免了許多無效匹配。
豌豆資源搜索網站 https://55wd.com 廣州vi設計公司http://www.maiqicn.com
四、CSS 語法解析過程
1.先創建 CSSStyleSheet 對象。將 CSSStyleSheet 對象的指針存儲到 CSSParser 對象中。
2.CSSParser 識別出一個 simple-selector ,形如 “div” 或者 “.class”。創建一個 CSSParserSelector 對象。
3.CSSParser 識別出一個關系符和另一個 simple-selecotr ,那么修改之前創建的 simple-selecotr, 創建組合關系符。
4.循環第3步直至碰到逗號或者左大括號。
5.如果碰到逗號,那么取出 CSSParser 的 reuse vector,然后將堆棧尾部的 CSSParserSelector 對象彈出存入 Vecotr 中,最后跳轉至第2步。如果碰到左大括號,那么跳轉至第6步。
6.識別屬性名稱,將屬性名稱的 hash 值壓入解釋器堆棧。
7.識別屬性值,創建 CSSParserValue 對象,並將 CSSParserValue 對象存入解釋器堆棧。
8.將屬性名稱和屬性值彈出棧,創建 CSSProperty 對象。並將 CSSProperty 對象存入 CSSParser 成員變量m_parsedProperties 中。
9.如果識別處屬性名稱,那么轉至第6步。如果識別右大括號,那么轉至第10步。
10.將 reuse vector 從堆棧中彈出,並創建 CSSStyleRule 對象。CSSStyleRule 對象的選擇符就是 reuse vector, 樣式值就是 CSSParser 的成員變量 m_parsedProperties 。
11.把 CSSStyleRule 添加到 CSSStyleSheet 中。
12.清空 CSSParser 內部緩存結果。
13.如果沒有內容了,那么結束。否則跳轉值第2步。