對于 csser 來說,多多少少都會遇到過 “樣式規則不生效?”、“樣式規則被覆蓋?” 等等問題,這些都與 CSS 權重有關系。
我自己是一名從事了多年開發的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:前端,即可免費獲取
選擇器匹配原理
在此之前,容我先簡單介紹瀏覽器是怎么通過各種選擇器,把樣式規則和 DOM 元素扯上關系的。
瀏覽器中存在著專門的渲染引擎來渲染 html 文檔。這里以 Webkit 內核為例,在啟動渲染流程時,引擎一方面會解析 HTML 文檔,構建 DOM 節點樹(DOM Tree),另一方面會解析樣式文件生成 樣式規則(Style Rules),然后結合分析 DOM 樹和樣式規則生成 渲染樹(Render Tree),最后 布局 和 繪制 出 UI 界面。
Webkit 渲染流程(摘自 https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/)
CSS 的選擇器匹配就發生在 渲染樹 的構建過程。瀏覽器會從 DOM 樹的根節點開始遍歷每個可見節點,對于每個可見節點都會在規則表中查找適配的樣式規則。那么,如此龐大的樣式數據和復雜的選擇器結構,渲染引擎是怎么尋找到適配當前元素的樣式規則呢?
請看下面這個復合選擇器。如果引擎是按照從左向右的順序匹配選擇器,將會導致大量 回溯的發生:先是在當前節點到 DOM 樹跟節點的路徑上尋找 div 元素,然后沿著分支路徑繼續往下找第二個 div 元素,如果當前路徑找不到,就得回退到上一個 div 元素嘗試另一條分支路徑。如此往復,對性能損耗將會非常嚴重。
div div span .text {}
所以,引擎是采取 從右向左 的順序來匹配選擇器。也就是 從最具體的選擇器開始,如果與當前節點不匹配,則直接拋棄該條規則;如果匹配,只需要沿著路徑往上確認其他選擇器是否也匹配,這樣做可以大大減少無效的匹配數,提高性能。除此之外,引擎還會把不同類型的選擇器(id、class、tag 及其他類型)歸類到哈希表中,進一步減少查找基數。
了解選擇器的匹配原理,有利于我們理解其權重規則,對于編寫簡潔、高效的 CSS 代碼非常有幫助。
CSS 權重
通過不同的方式(內聯樣式、外部樣式表)、不同類型的選擇器組合針對某個元素聲明樣式規則時,如何決定最終哪個聲明會被應用到元素上?這就涉及到 CSS 權重(也指優先級,Specificity)。
圍繞 CSS 權重主要有以下三條規則:
- 權重不同的樣式規則作用于同一元素時,權重高的規則生效;
- 權重相同的樣式規則作用于同一元素時,后聲明的規則生效;
- 選擇器在 DOM 中的位置關系不會對規則產生影響。
<html> <head> <style> body div { color: red; } html div { color: blue; } </style> </head> <body> <div>測試</div> </body> <html>
- 這里的 body 標簽元素在 DOM 中離目標 div 更近,但最后還是按照樣式規則的聲明順序來決定。
- 直接作用于元素的樣式規則優先級高于從祖先元素繼承的規則;
<html> <head> <style> #parent { color: red; } span { color: blue; } </style> </head> <body> <div id="parent"> <span>測試</span> </div> </body> <html>
CSS 權重等級
如何比較不同選擇器的權重高低?這里劃分成 5 個權重等級,按照等級 由高到低 的順序:
- !important 關鍵字
- 內聯樣式
<div style="color: #fff;">測試</div>
id 選擇器
#demo {}
類選擇器、屬性選擇器、偽類選擇器
.demo {} [type="text"] {} div:hover {} div:first-child {}
需要注意,否定偽類(:not())比較特殊,它不會對權重產生影響,但是 否定偽類內部的選擇器會影響權重。
<html> <head> <style> div#demo span { color: red; } div:not(#demo) span { color: blue; } </style> </head> <body> <div id="demo"> <span>普通 demo</span> <div id="pseudo"> <span>否定偽類 demo</span> </div> </div> </body> <html>
- 實例中,:not(#demo) 的權重值和 #demo 的權重值是相等的,所以后面聲明的樣式規則成功生效。
- 標簽選擇器、偽元素選擇器
div {} div:before {} div:after {}
除了上述的選擇器之外,通配符選擇器(*) 和 結合符(+、>、~)對優先級沒有影響。
對于復雜的復合選擇器,我們需要逐個等級比較權重大小,不允許跨越等級比較。為了方便計算,我們可以把權重值具象化,每出現一個選擇器就在其對應的等級區間中權重值加 1,參考下面實例:
* {} /* 權重值 0-0-0-0-0 */ div {} /* 權重值 0-0-0-0-1 */ div h1+h2 {} /* 權重值 0-0-0-0-3 */ div, ... div {} /* 權重值 0-0-0-0-n */ #demo a:hover {} /* 權重值 0-0-1-1-1 */
國外大神 把 CSS 權重的計算模擬成海洋生物鏈,選擇器組合權重越大則在生物鏈位置越高,非常淺顯生動,建議收藏。
圖片轉自 https://specifishity.com/
建議
在充分了解 CSS 選擇器匹配原理和權重規則之后,在編寫 CSS 代碼時不妨多注意以下細節:
- 盡量不要使用 !important,尤其是在 對外提供的插件 和 全站范圍的樣式表 中,這會對模塊代碼中的樣式覆蓋帶來非常大的麻煩。
- !important 關鍵字的權重值為 1-0-0-0-0,只需要按照權重規則繼續累加權重值即可覆蓋該樣式屬性。
<html> <head> <style> div { color: red !important; } /* 通過 id選擇器 增加權重 */ #demo { color: blue !important; } </style> </head> <body> <div id="demo">測試</div> </body> <html>
減少不必要的選擇器嵌套,嵌套最好不要超過三級。大量的復合選擇器,會影響選擇器匹配的效率,同時也會增加 CSS 樣式文件的體積,不易維護。
當出現大量嵌套時,我們可以指定一個更具體的類選擇器來替換復合選擇器。
body div ul li span {} .li {}