優先順序
優先順序(Specificity)是瀏覽器用來決定哪個 CSS 宣告與元素最相關,從而決定該元素的屬性值的演算法。優先順序演算法透過計算 CSS 選擇器的權重,來決定相互競爭的 CSS 宣告中哪一個規則會被應用到元素上。
如何計算優先順序?
優先順序是一種演算法,用於計算應用於給定 CSS 宣告的權重。權重由匹配元素(或偽元素)的選擇器中每個權重類別的選擇器數量決定。如果有兩個或更多的宣告為同一元素提供不同的屬性值,那麼具有最大演算法權重的匹配選擇器的樣式塊中的宣告值將被應用。
優先順序演算法基本上是一個由三個類別或權重——ID、CLASS 和 TYPE——組成的三列值,對應三種類型的選擇器。該值表示每個權重類別中選擇器元件的數量,並寫為 ID - CLASS - TYPE。這三列是透過計算匹配元素的選擇器中每個選擇器權重類別的選擇器元件數量來建立的。
選擇器權重類別
選擇器權重類別按優先順序從高到低排列如下
- ID 列
-
僅包括 ID 選擇器,例如
#example。對於匹配選擇器中的每個 ID,權重值增加 1-0-0。 - CLASS 列
-
包括類選擇器,例如
.myClass,屬性選擇器如[type="radio"]和[lang|="fr"],以及偽類,例如:hover、:nth-of-type(3n)和:required。對於匹配選擇器中的每個類、屬性選擇器或偽類,權重值增加 0-1-0。 - TYPE 列
-
包括型別選擇器,例如
p、h1和td,以及偽元素如::before、::placeholder和所有其他使用雙冒號表示法的選擇器。對於匹配選擇器中的每個型別或偽元素,權重值增加 0-0-1。 - 無值
-
通用選擇器 (
*) 和偽類:where()及其引數在計算權重時不被計算,所以它們的值是 0-0-0,但它們確實能匹配元素。這些選擇器不影響優先順序權重值。
組合器,如 +、>、~、" " (空格) 和 ||,可能會使選擇器在選擇目標上更具體,但它們不會增加任何優先順序權重值。
& 巢狀組合器不增加優先順序權重,但巢狀規則會。在優先順序和功能方面,巢狀與 :is() 偽類非常相似。
與巢狀類似,:is()、:has() 和否定 (:not()) 偽類本身不增加權重。但是,這些選擇器中的引數會增加權重。它們的優先順序權重來自於選擇器列表中具有最高優先順序的選擇器引數。同樣,對於巢狀選擇器,由巢狀選擇器元件增加的優先順序權重是逗號分隔的巢狀選擇器列表中具有最高優先順序的選擇器。
下文將討論 :not()、:is()、:has() 和 CSS 巢狀的例外情況。
匹配的選擇器
優先順序權重來自於匹配的選擇器。以這個包含三個逗號分隔選擇器的 CSS 選擇器為例
[type="password"],
input:focus,
:root #myApp input:required {
color: blue;
}
上述選擇器列表中的 [type="password"] 選擇器,其優先順序權重為 0-1-0,將 color: blue 宣告應用於所有密碼輸入型別。
所有 input,無論型別如何,在獲得焦點時,都會匹配列表中的第二個選擇器 input:focus,其優先順序權重為 0-1-1;這個權重由 :focus 偽類 (0-1-0) 和 input 型別 (0-0-1) 組成。如果密碼輸入框有焦點,它將匹配 input:focus,並且 color: blue 樣式宣告的優先順序權重將是 0-1-1。當該密碼輸入框沒有焦點時,優先順序權重仍為 0-1-0。
一個巢狀在具有 id="myApp" 屬性的元素中的 required input 的優先順序是 1-2-1,基於一個 ID、兩個偽類和一個元素型別。
如果帶有 required 的密碼輸入型別巢狀在一個設定了 id="myApp" 的元素中,無論它是否獲得焦點,其優先順序權重都將是 1-2-1,基於一個 ID、兩個偽類和一個元素型別。為什麼在這種情況下優先順序權重是 1-2-1 而不是 0-1-1 或 0-1-0?因為優先順序權重來自於具有最高優先順序權重的匹配選擇器。權重是透過從左到右比較三列中的值來確定的。
[type="password"] {
/* 0-1-0 */
}
input:focus {
/* 0-1-1 */
}
:root #myApp input:required {
/* 1-2-1 */
}
三列比較
一旦確定了相關選擇器的優先順序值,就會從左到右比較每列中選擇器元件的數量。
#myElement {
color: green; /* 1-0-0 - WINS!! */
}
.bodyClass .sectionClass .parentClass [id="myElement"] {
color: yellow; /* 0-4-0 */
}
第一列是 ID 元件的值,也就是每個選擇器中 ID 的數量。比較相互競爭的選擇器的 ID 列中的數字。在 ID 列中值較大的選擇器獲勝,無論其他列中的值是多少。在上面的例子中,儘管黃色選擇器總共有更多的元件,但只有第一列的值起作用。
如果相互競爭的選擇器在 ID 列中的數字相同,那麼就比較下一列,即 CLASS 列,如下所示。
#myElement {
color: yellow; /* 1-0-0 */
}
#myApp [id="myElement"] {
color: green; /* 1-1-0 - WINS!! */
}
CLASS 列是選擇器中類名、屬性選擇器和偽類的計數。當 ID 列的值相同時,CLASS 列中值較大的選擇器獲勝,無論 TYPE 列中的值是多少。下面的例子說明了這一點。
:root input {
color: green; /* 0-1-1 - WINS because CLASS column is greater */
}
html body main input {
color: yellow; /* 0-0-4 */
}
如果相互競爭的選擇器在 CLASS 和 ID 列中的數字都相同,TYPE 列就變得相關了。TYPE 列是選擇器中元素型別和偽元素的數量。當前兩列的值相同時,TYPE 列中數字較大的選擇器獲勝。
如果相互競爭的選擇器在所有三列中的值都相同,那麼鄰近規則就會生效,即最後宣告的樣式具有優先權。
input.myClass {
color: yellow; /* 0-1-1 */
}
:root input {
color: green; /* 0-1-1 WINS because it comes later */
}
:is()、:not()、:has() 和 CSS 巢狀的例外情況
任意匹配偽類 :is()、關係偽類 :has() 和否定偽類 :not() 在優先順序權重計算中不被視為偽類。它們本身不會給優先順序等式增加任何權重。然而,傳遞到偽類括號中的選擇器引數是優先順序演算法的一部分;任意匹配和否定偽類在優先順序值計算中的權重是其引數的權重。
p {
/* 0-0-1 */
}
:is(p) {
/* 0-0-1 */
}
h2:nth-last-of-type(n + 2) {
/* 0-1-1 */
}
h2:has(~ h2) {
/* 0-0-2 */
}
div.outer p {
/* 0-1-2 */
}
div:not(.inner) p {
/* 0-1-2 */
}
注意,在上面的 CSS 對中,由 :is()、:has() 和 :not() 偽類提供的優先順序權重是選擇器引數的值,而不是偽類本身的值。
這三個偽類都接受複雜的選擇器列表,即一個由逗號分隔的選擇器組成的列表,作為引數。這個特性可以用來增加一個選擇器的優先順序
:is(p, #fakeId) {
/* 1-0-0 */
}
h1:has(+ h2, > #fakeId) {
/* 1-0-1 */
}
p:not(#fakeId) {
/* 1-0-1 */
}
div:not(.inner, #fakeId) p {
/* 1-0-2 */
}
在上面的 CSS 程式碼塊中,我們在選擇器中包含了 #fakeId。這個 #fakeId 為每個段落的優先順序權重增加了 1-0-0。
當使用 CSS 巢狀建立複雜的選擇器列表時,其行為與 :is() 偽類完全相同。
p,
#fakeId {
span {
/* 1-0-1 */
}
}
在上面的程式碼塊中,複雜選擇器 p, #fakeId 的優先順序取自 #fakeId 和 span,因此對於 p span 和 #fakeId span 都建立了一個 1-0-1 的優先順序。這與 :is(p, #fakeId) span 選擇器的優先順序是等效的。
通常,你會希望將優先順序保持在最低水平,但如果出於特定原因需要增加元素的優先順序,這三個偽類可以提供幫助。
a:not(#fakeId#fakeId#fakeID) {
color: blue; /* 3-0-1 */
}
在這個例子中,所有連結都將是藍色的,除非被一個包含 3 個或更多 ID 的連結宣告覆蓋,或者匹配 a 的顏色值包含 !important 標誌,或者連結有內聯樣式顏色宣告。如果你使用這種技術,請添加註釋來解釋為什麼需要這個技巧。
內聯樣式
新增到元素上的內聯樣式(例如,style="font-weight: bold;")總是會覆蓋作者樣式表中的任何普通樣式,因此,可以認為它具有最高的優先順序。可以把內聯樣式看作是具有 1-0-0-0 的優先順序權重。
覆蓋內聯樣式的唯一方法是使用 !important。
許多 JavaScript 框架和庫會新增內聯樣式。使用 !important 配合一個非常具體的目標選擇器,例如使用內聯樣式的屬性選擇器,是覆蓋這些內聯樣式的一種方法。
<p style="color: purple">…</p>
p[style*="purple"] {
color: rebeccapurple !important;
}
確保在每次使用 important 標誌時都包含註釋,以便程式碼維護者理解為什麼使用了這個 CSS 反模式。
!important 例外
標記為 important 的 CSS 宣告會覆蓋同一層疊層和來源中的任何其他宣告。雖然從技術上講,!important 與優先順序無關,但它直接與優先順序和層疊互動。它顛倒了樣式表的層疊順序。
如果來自相同來源和層疊層的聲明發生衝突,並且一個屬性值設定了 !important 標誌,那麼無論優先順序如何,都會應用這個 important 宣告。當來自相同來源和層疊層且帶有 !important 標誌的衝突宣告應用於同一元素時,具有更高優先順序的宣告將被應用。
使用 !important 來覆蓋優先順序被認為是一種不良實踐,應避免用於此目的。理解並有效使用優先順序和層疊可以消除任何對 !important 標誌的需求。
與其使用 !important 來覆蓋外部 CSS(來自外部庫,如 Bootstrap 或 normalize.css),不如將第三方指令碼直接匯入到層疊層中。如果你必須在你的 CSS 中使用 !important,請對你的用法進行註釋,以便未來的程式碼維護者知道為什麼該宣告被標記為 important,並知道不要覆蓋它。但絕對不要在編寫外掛或框架時使用 !important,因為其他開發者需要將它們整合進來,而無法控制。
:where() 例外
優先順序調整偽類 :where() 的優先順序總是被替換為零,即 0-0-0。它使得 CSS 選擇器在目標元素上非常具體,而不會增加任何優先順序。
在建立供無法編輯你 CSS 的開發者使用的第三方 CSS 時,建立具有儘可能低優先順序的 CSS 被認為是一種良好實踐。例如,如果你的主題包含以下 CSS
:where(#defaultTheme) a {
/* 0-0-1 */
color: red;
}
那麼實現該小部件的開發者可以僅使用型別選擇器輕鬆地覆蓋連結顏色。
footer a {
/* 0-0-2 */
color: blue;
}
@scope 塊如何影響優先順序
將規則集包含在 @scope 塊內不會影響其選擇器的優先順序,無論作用域根和限制中使用了什麼選擇器。但是,如果你決定顯式新增 :scope 偽類,你需要在計算它們的優先順序時將其考慮在內。:scope,像所有常規偽類一樣,其優先順序為 0-1-0。例如
@scope (.article-body) {
/* :scope img has a specificity of 0-1-0 + 0-0-1 = 0-1-1 */
:scope img {
}
}
有關更多資訊,請參閱@scope 中的優先順序。
處理優先順序難題的技巧
與其使用 !important,不如考慮使用層疊層,並在你的整個 CSS 中使用低權重的優先順序,以便樣式可以被稍微更具體的規則輕鬆覆蓋。使用語義化 HTML 有助於提供應用樣式的錨點。
使選擇器更具體,同時增加或不增加優先順序
透過在你選擇的元素之前指明你正在設定樣式的文件部分,規則會變得更具體。根據你新增它的方式,你可以增加一些、很多或不增加優先順序,如下所示
<main id="myContent">
<h1>Text</h1>
</main>
#myContent h1 {
color: green; /* 1-0-1 */
}
[id="myContent"] h1 {
color: yellow; /* 0-1-1 */
}
:where(#myContent) h1 {
color: blue; /* 0-0-1 */
}
無論順序如何,標題都將是綠色的,因為該規則是最具體的。
降低 ID 優先順序
優先順序是基於選擇器的形式的。將元素的 id 作為屬性選擇器而不是 id 選擇器包含進來,是使元素更具體而不增加過多優先順序的好方法。在前面的例子中,選擇器 [id="myContent"] 在確定選擇器優先順序時算作一個屬性選擇器,即使它選擇的是一個 ID。
如果你需要使一個選擇器更具體但又不想增加任何優先順序,你也可以將 id 或選擇器的任何部分作為引數包含在 :where() 優先順序調整偽類中。
透過重複選擇器來增加優先順序
作為增加優先順序的一種特殊情況,你可以重複 CLASS 或 ID 列的權重。在一個複合選擇器中重複 id、class、偽類或屬性選擇器,將在覆蓋你無法控制的非常具體的選擇器時增加優先順序。
#myId#myId#myId span {
/* 3-0-1 */
}
.myClass.myClass.myClass span {
/* 0-3-1 */
}
謹慎使用此方法,如果可以的話儘量不要用。如果使用選擇器重複,請務必在你的 CSS 中添加註釋。
透過使用 :is() 和 :not()(以及 :has()),即使你無法向父元素新增 id,也可以增加優先順序
:not(#fakeID#fakeId#fakeID) span {
/* 3-0-1 */
}
:is(#fakeID#fakeId#fakeID, span) {
/* 3-0-0 */
}
優先於第三方 CSS
利用層疊層是使一組樣式優先於另一組樣式的標準方法;層疊層可以在不使用優先順序的情況下實現這一點!匯入到層疊層中的普通(非 important)作者樣式比未分層的作者樣式具有更低的優先順序。
如果樣式來自你無法編輯或不瞭解的樣式表,而你需要覆蓋樣式,一個策略是將你不受控制的樣式匯入到一個層疊層中。後續宣告的層中的樣式具有優先權,而未分層的樣式優先於來自同一來源的所有分層樣式。
當來自不同層的兩個選擇器匹配同一個元素時,來源和重要性優先;失敗的樣式表中的選擇器的優先順序無關緊要。
@import "TW.css" layer();
p,
p * {
font-size: 1rem;
}
在上面的例子中,所有段落文字,包括巢狀內容,都將是 1rem,無論這些段落有多少個匹配 TW 樣式表的類名。
避免和覆蓋 !important
最好的方法是不使用 !important。上面關於優先順序的解釋應該有助於避免使用該標誌,並在遇到它時完全移除它。
要消除對 !important 的感知需求,你可以執行以下操作之一
- 增加先前
!important宣告的選擇器的優先順序,使其大於其他宣告 - 給它相同的優先順序,並將其放在它要覆蓋的宣告之後
- 降低你試圖覆蓋的選擇器的優先順序。
所有這些方法在前面的章節中都有介紹。
如果你無法從作者的樣式表中移除 !important 標誌,那麼覆蓋這些 important 樣式的唯一解決方案就是使用 !important。建立一個層疊層來覆蓋 important 宣告是一個很好的解決方案。有兩種方法可以做到這一點
方法 1
- 建立一個單獨的、簡短的樣式表,只包含專門用於覆蓋你無法移除的任何 important 宣告的 important 宣告。
- 在連結到其他樣式表之前,使用
layer()將此樣式表作為你的 CSS 中的第一個匯入,包括@import語句。這是為了確保 important 覆蓋作為第一層被匯入。
@import "importantOverrides.css" layer();
方法 2
-
在你的樣式表宣告的開頭,建立一個命名的層疊層,像這樣
css@layer importantOverrides; -
每次你需要覆蓋一個 important 宣告時,就在這個命名的層內宣告它。只在層內宣告 important 規則。
css[id="myElement"] p { /* normal styles here */ } @layer importantOverrides { [id="myElement"] p { /* important style here */ } }
層內 important 樣式的選擇器優先順序可以很低,只要它能匹配你試圖覆蓋的元素即可。普通層應在層外宣告,因為分層樣式比未分層樣式具有更低的優先順序。
忽略樹的鄰近度
一個元素與給定選擇器中引用的其他元素的鄰近度對優先順序沒有影響。
body h1 {
color: green;
}
html h1 {
color: purple;
}
<h1> 元素將是紫色的,因為當宣告具有相同的優先順序時,最後宣告的選擇器具有優先權。
直接目標元素 vs. 繼承樣式
直接目標元素的樣式總是優先於繼承的樣式,無論繼承規則的優先順序如何。給定以下 CSS 和 HTML
#parent {
color: green;
}
h1 {
color: purple;
}
<html lang="en">
<body id="parent">
<h1>Here is a title!</h1>
</body>
</html>
h1 將是紫色的,因為 h1 選擇器專門針對該元素,而綠色是從 #parent 宣告中繼承的。
示例
在下面的 CSS 中,我們有三個選擇器針對 <input> 元素來設定顏色。對於給定的 input,具有優先權的顏色宣告的優先順序權重是匹配選擇器中權重最大的那個
#myElement input.myClass {
color: red;
} /* 1-1-1 */
input[type="password"]:required {
color: blue;
} /* 0-2-1 */
html body main input {
color: green;
} /* 0-0-4 */
如果以上所有選擇器都指向同一個 input,那麼這個 input 將是紅色的,因為第一個宣告在 ID 列中具有最高的值。
最後一個選擇器有四個 TYPE 元件。雖然它的整數值最高,但無論包含多少元素和偽元素,即使有 150 個,TYPE 元件也永遠不會優先於 CLASS 元件。當列值相等時,會從左到右比較列值。
如果我們將上面示例程式碼中的 id 選擇器轉換為屬性選擇器,那麼前兩個選擇器將具有相同的優先順序,如下所示
[id="myElement"] input.myClass {
color: red;
} /* 0-2-1 */
input[type="password"]:required {
color: blue;
} /* 0-2-1 */
當多個宣告具有相等的優先順序時,在 CSS 中找到的最後一個宣告將應用於該元素。如果兩個選擇器都匹配同一個 <input>,顏色將是藍色的。
補充說明
關於優先順序需要記住的幾件事
-
優先順序僅在同一元素被同一層疊層或來源中的多個宣告定位時適用。優先順序僅對具有相同重要性、相同來源和層疊層的宣告有意義。如果匹配的選擇器在不同的來源中,則由層疊決定哪個宣告優先。
-
當同一層疊層和來源中的兩個選擇器具有相同的優先順序時,接著會計算作用域鄰近度;作用域鄰近度最低的規則集獲勝。有關更多詳細資訊和示例,請參閱
@scope衝突如何解決。 -
如果兩個選擇器的作用域鄰近度也相同,那麼源順序就會起作用。當所有其他條件都相等時,最後一個選擇器獲勝。
-
根據 CSS 規則,直接目標元素將總是優先於元素從其祖先繼承的規則。
-
文件樹中元素的鄰近度對優先順序沒有影響。
規範
| 規範 |
|---|
| 選擇器 Level 4 # 優先順序規則 |