特異性

Specificity 是瀏覽器用來確定 CSS 宣告 對元素最相關的演算法,它決定了應用於元素的屬性值。Specificity 演算法計算 CSS 選擇器 的權重,以確定來自競爭 CSS 宣告的哪個規則將應用於元素。

注意: 瀏覽器在確定 級聯來源和重要性 之後才會考慮 specificity。換句話說,對於競爭的屬性宣告,specificity 僅在來自對屬性具有優先順序的 級聯來源和層 的選擇器之間相關且進行比較。當級聯層中具有優先順序的競爭宣告的選擇器 specificity 相等時,作用域鄰近 和出現順序將變得相關。

如何計算特異性?

Specificity 是一種演算法,用於計算應用於給定 CSS 宣告的權重。權重由匹配元素(或偽元素)的選擇器中 每個權重類別中的選擇器數量 決定。如果有兩個或多個宣告為同一個元素提供不同的屬性值,則具有匹配選擇器的樣式塊中的宣告值將具有最大的演算法權重並被應用。

Specificity 演算法基本上是一個三列值,包含三個類別或權重 - ID、CLASS 和 TYPE,對應於三種類型的選擇器。該值表示每個權重類別中匹配元素的選擇器元件的數量,並寫為 ID - CLASS - TYPE。這三列是透過計算與元素匹配的選擇器中每個選擇器權重類別中的選擇器元件數量來建立的。

選擇器權重類別

選擇器權重類別按 specificity 遞減的順序列出

ID 列

僅包含 ID 選擇器,例如 #example。對於匹配選擇器中的每個 ID,將 1-0-0 新增到權重值。

CLASS 列

包括 類選擇器,例如 .myClass,屬性選擇器,例如 [type="radio"][lang|="fr"],以及偽類,例如 :hover:nth-of-type(3n):required。 對於匹配選擇器中的每個類、屬性選擇器或偽類,將 0-1-0 新增到權重值。

TYPE 列

包括 型別選擇器,例如 ph1td,以及偽元素,例如 ::before::placeholder,以及所有其他帶有雙冒號表示法的選擇器。 對於匹配選擇器中的每個型別或偽元素,將 0-0-1 新增到權重值。

無值

通用選擇器 (*) 以及偽類 :where() 及其引數在計算權重時不計算,因此其值為 0-0-0,但它們確實匹配元素。 這些選擇器不會影響特異性權重值。

組合器,例如 +>~" "||,可能會使選擇器在選擇方面更具體,但它們不會向特異性權重新增任何值。

& 巢狀組合器不會增加特異性權重,但巢狀規則會。 在特異性和功能方面,巢狀與 :is() 偽類非常相似。

與巢狀類似,:is():has() 和否定 (:not()) 偽類本身不會新增任何權重。 然而,這些選擇器中的引數確實會。 每個的權重來自選擇器引數列表中特異性最高的那個選擇器。 同樣,對於巢狀選擇器,巢狀選擇器元件新增的特異性權重是逗號分隔的巢狀選擇器列表中特異性最高的選擇器。

:not():is():has() 和 CSS 巢狀例外情況 在下面討論。

匹配選擇器

特異性權重來自匹配選擇器。 以這個具有三個逗號分隔選擇器的 CSS 選擇器為例

css
[type="password"],
input:focus,
:root #myApp input:required {
  color: blue;
}

上面的選擇器列表中的 [type="password"] 選擇器,特異性權重為 0-1-0,將 color: blue 宣告應用於所有密碼輸入型別。

所有輸入,無論型別如何,在獲得焦點時,都會匹配列表中的第二個選擇器 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" 屬性的元素中的必需輸入的特異性為 1-2-1,基於一個 ID,兩個偽類和一個元素型別。

如果具有 required 的密碼輸入型別巢狀在具有 id="myApp" 設定的元素中,特異性權重將為 1-2-1,基於一個 ID,兩個偽類和一個元素型別,無論它是否有焦點。 為什麼在這種情況下特異性權重是 1-2-1 而不是 0-1-10-1-0? 因為特異性權重來自特異性權重最大的匹配選擇器。 權重是透過從左到右比較三列中的值來確定的。

css
[type="password"]             /* 0-1-0 */
input:focus                   /* 0-1-1 */
:root #myApp input:required   /* 1-2-1 */

三列比較

確定相關選擇器的特異性值後,從左到右比較每列中選擇器元件的數量。

css
#myElement {
  color: green; /* 1-0-0  - WINS!! */
}
.bodyClass .sectionClass .parentClass [id="myElement"] {
  color: yellow; /* 0-4-0 */
}

第一列是ID 元件的值,它是每個選擇器中 ID 的數量。 比較競爭選擇器中ID 列中的數字。 無論其他列中的值如何,ID 列中值更大的選擇器獲勝。 在上面的示例中,即使黃色選擇器總共包含更多元件,但只有第一列的值才重要。

如果競爭選擇器中ID 列中的數字相同,則比較下一列CLASS,如下所示。

css
#myElement {
  color: yellow; /* 1-0-0 */
}
#myApp [id="myElement"] {
  color: green; /* 1-1-0  - WINS!! */
}

CLASS 列是選擇器中類名、屬性選擇器和偽類的計數。 當ID 列的值相同時,無論TYPE 列中的值如何,CLASS 列中值更大的選擇器獲勝。 這在下面的示例中顯示。

css
:root input {
  color: green; /* 0-1-1 - WINS because CLASS column is greater */
}
html body main input {
  color: yellow; /* 0-0-4 */
}

如果競爭選擇器中CLASSID 列中的數字相同,則TYPE 列變得相關。 TYPE 列是選擇器中元素型別和偽元素的數量。 當前兩列的值相同時,TYPE 列中數字更大的選擇器獲勝。

如果競爭選擇器在所有三列中都具有相同的值,則鄰近規則生效,其中最後宣告的樣式具有優先順序。

css
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() 在特異性權重計算中被視為偽類。 它們本身不會向特異性方程式新增任何權重。 但是,傳遞到偽類括號中的選擇器引數是特異性演算法的一部分; 特異性值計算中匹配任何和否定偽類的權重是引數的 權重

css
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() 偽類提供的特異性權重是選擇器引數的值,而不是偽類的值。

所有這三個偽類都接受複雜選擇器列表,一個逗號分隔的選擇器列表,作為引數。 此功能可用於提高選擇器的特異性

css
: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。 此 #fakeId1-0-0 新增到每個段落的特異性權重。

當使用 CSS 巢狀 建立複雜選擇器列表時,其行為與 :is() 偽類完全相同。

css
p,
#fakeId {
  span {
    /* 1-0-1 */
  }
}

在上面的程式碼塊中,複雜選擇器 p, #fakeId 的特異性來自 #fakeId 以及 span,因此這對 p span#fakeId span 建立了 1-0-1 的特異性。 這與 :is(p, #fakeId) span 選擇器的特異性相同。

通常,您希望將特異性保持在最低限度,但如果您需要出於特定原因提高元素的特異性,這三個偽類可以提供幫助。

css
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 與非常有針對性的選擇器(例如使用內聯樣式的屬性選擇器)是覆蓋這些內聯樣式的一種方法。

html
<p style="color: purple"></p>
css
p[style*="purple"] {
  color: rebeccapurple !important;
}

確保在包含重要標誌的每個位置都包含註釋,以便程式碼維護人員瞭解為什麼使用 CSS 反模式,並且知道不要覆蓋它。

!important 例外

標記為重要的 CSS 宣告會覆蓋相同級聯層和來源中的任何其他宣告。 雖然在技術上,!important 與特異性無關,但它與特異性和級聯直接互動。 它反轉了樣式表的 級聯 順序。

如果來自相同來源和級聯層的聲明發生衝突,並且一個屬性值設定了 !important 標誌,則無論特異性如何,都將應用重要宣告。 當來自相同來源和級聯層並且具有 !important 標誌的衝突宣告應用於同一元素時,將應用特異性更高的宣告。

使用 !important 覆蓋特異性被認為是不良做法,應避免為此目的使用它。 瞭解和有效地使用特異性和級聯可以消除對 !important 標誌的任何需求。

不要使用 !important 覆蓋外部 CSS(來自外部庫,如 Bootstrap 或 normalize.css),而是將第三方指令碼直接匯入 級聯層。 如果你必須在你的 CSS 中使用 !important,請註釋你的用法,以便未來的程式碼維護人員知道為什麼宣告被標記為重要,並且知道不要覆蓋它。 但是,在編寫其他開發人員需要合併而無法控制的外掛或框架時,絕對不要使用 !important

:where() 例外

特異性調整偽類 :where() 的特異性始終被替換為零,0-0-0。 它能夠在不增加特異性的情況下,使 CSS 選擇器對目標元素非常具體。

在建立供沒有訪問許可權編輯你的 CSS 的開發人員使用的第三方 CSS 時,建立特異性儘可能低的 CSS 被認為是一種良好的做法。 例如,如果你的主題包含以下 CSS

css
:where(#defaultTheme) a {
  /* 0-0-1 */
  color: red;
}

那麼實現小部件的開發人員可以輕鬆地僅使用型別選擇器覆蓋連結顏色。

css
footer a {
  /* 0-0-2 */
  color: blue;
}

@scope 塊如何影響特異性

將規則集包含在 @scope 塊中不會影響其選擇器的特異性,無論在範圍根和限制內使用什麼選擇器。 例如

css
@scope (.article-body) {
  /* img has a specificity of 0-0-1, as expected */
  img { ... }
}

但是,如果您決定將 :scope 偽類顯式地附加到您的範圍選擇器,則在計算其特異性時需要將其考慮在內。 :scope 與所有常規偽類一樣,具有 0-1-0 的特異性。 例如

css
@scope (.article-body) {
  /* :scope img has a specificity of 0-1-0 + 0-0-1 = 0-1-1 */
  :scope img { ... }
}

@scope 塊中使用 & 選擇器時,& 代表範圍根選擇器; 它會在內部被重寫為包裝在 :is() 選擇器中的該選擇器。 因此,例如,在

css
@scope (figure, #primary) {
  & img { ... }
}

& img 等同於 :is(figure, #primary) img

由於 :is() 採用其最具體引數(在本例中為 #primary)的特異性,因此範圍 & img 選擇器的特異性因此為 1-0-0 + 0-0-1 = 1-0-1。

處理特異性問題的技巧

除了使用 !important 之外,還可以考慮使用級聯層並在整個 CSS 中使用低權重特異性,以便使用稍微更具體規則可以輕鬆地覆蓋樣式。 使用語義 HTML 有助於提供應用樣式的錨點。

在新增和不新增特異性的情況下使選擇器更具體

透過在選擇元素之前指明正在設定樣式的文件部分,規則變得更具體。 根據你的新增方式,你可以新增一些、很多或不新增特異性,如下所示

html
<main id="myContent">
  <h1>Text</h1>
</main>
css
#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()特定性調整偽類中。

透過複製選擇器來增加特定性

作為增加特定性的一個特例,您可以複製CLASSID列中的權重。在複合選擇器中重複 id、類、偽類或屬性選擇器將增加特定性,以便在覆蓋您無法控制的非常特定的選擇器時。

css
#myId#myId#myId span {
  /* 3-0-1 */
}
.myClass.myClass.myClass span {
  /* 0-3-1 */
}

謹慎使用,如果有的話。如果使用選擇器重複,請始終對您的 CSS 進行註釋。

透過使用:is():not()(以及:has()),即使您無法向父元素新增id,也可以提高特定性。

css
:not(#fakeID#fakeId#fakeID) span {
  /* 3-0-1 */
}
:is(#fakeID#fakeId#fakeID, span) {
  /* 3-0-0 */
}

優先於第三方 CSS

利用級聯層是使一組樣式優先於另一組樣式的標準方法;級聯層使這成為可能,而無需使用特定性!匯入到級聯層中的普通(非重要)作者樣式比非分層作者樣式的優先順序低。

如果樣式來自您無法編輯或不理解的樣式表,並且您需要覆蓋樣式,則可以使用一種策略將您無法控制的樣式匯入到級聯層中。後續宣告的層中的樣式具有優先順序,非分層樣式比來自相同來源的所有分層樣式具有優先順序。

當來自不同層的兩個選擇器匹配同一個元素時,來源和重要性優先;失敗的樣式表的特定性與選擇器的特定性無關。

html
<style>
  @import TW.css layer();
  p,
  p * {
    font-size: 1rem;
  }
</style>

在上面的示例中,所有段落文字,包括巢狀內容,都將是1rem,無論段落有多少類名稱與 TW 樣式表匹配。

避免和覆蓋!important

最好的方法是不使用!important。以上關於特定性的解釋應該有助於避免使用該標誌,並在遇到時完全將其刪除。

要消除對!important的感知需求,您可以執行以下操作之一

  • 提高以前!important宣告的選擇器的特定性,使其大於其他宣告。
  • 給予它相同的特定性,並將其放在它要覆蓋的宣告之後。
  • 降低要覆蓋的選擇器的特定性。

所有這些方法都在前面的部分中介紹。

如果您無法從作者樣式表中刪除!important標誌,則覆蓋重要樣式的唯一解決方案是使用!important。建立重要的宣告覆蓋的級聯層是一個很好的解決方案。有兩種方法可以做到這一點,包括

方法 1

  1. 建立一個單獨的簡短樣式表,其中只包含重要的宣告,專門覆蓋您無法刪除的任何重要宣告。
  2. 使用layer()將此樣式表作為第一個匯入匯入到您的 CSS 中,包括@import語句,然後再連結到其他樣式表。這是為了確保重要的覆蓋作為第一層匯入。
html
<style>
  @import importantOverrides.css layer();
</style>

方法 2

  1. 在您的樣式表宣告的開頭,建立一個命名的級聯層,如下所示
    css
    @layer importantOverrides;
    
  2. 每次您需要覆蓋重要的宣告時,都在命名的層中宣告它。只在層中宣告重要的規則。
    css
    [id="myElement"] p {
      /* normal styles here */
    }
    @layer importantOverrides {
      [id="myElement"] p {
        /* important style here */
      }
    }
    

層中重要樣式的選擇器的特定性可以很低,只要它與您要覆蓋的元素匹配即可。普通層應該在層外宣告,因為分層樣式比非分層樣式的優先順序低。

樹鄰近性忽略

元素與給定選擇器中引用的其他元素的鄰近性對特定性沒有影響。

css
body h1 {
  color: green;
}

html h1 {
  color: purple;
}

<h1>元素將是紫色的,因為當宣告具有相同的特定性時,最後宣告的選擇器具有優先順序。

直接目標元素與繼承樣式

直接目標元素的樣式始終優先於繼承的樣式,無論繼承規則的特定性如何。給定以下 CSS 和 HTML

css
#parent {
  color: green;
}

h1 {
  color: purple;
}
html
<html lang="en">
  <body id="parent">
    <h1>Here is a title!</h1>
  </body>
</html>

h1將是紫色的,因為h1選擇器專門針對元素,而綠色是從#parent宣告中繼承的。

示例

在以下 CSS 中,我們有三個選擇器針對<input>元素來設定顏色。對於給定的輸入,具有優先順序的顏色宣告的特定性權重是具有最大權重的匹配選擇器。

css
#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 */

如果以上選擇器都針對同一個輸入,則輸入將是紅色的,因為第一個宣告在ID列中具有最高值。

最後一個選擇器有四個TYPE元件。雖然它具有最高的整數價值,但無論包含多少個元素和偽元素,即使有 150 個,TYPE元件永遠不會優先於CLASS元件。當列值相等時,從左到右開始比較列值。

如果我們將上面的示例程式碼中的 id 選擇器轉換為屬性選擇器,則前兩個選擇器將具有相同的特定性,如下所示

css
[id="myElement"] input.myClass {
  color: red;
} /* 0-2-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */

當多個宣告具有相同的特定性時,CSS 中找到的最後一個宣告將應用於元素。如果兩個選擇器都匹配同一個<input>,則顏色將是藍色的。

附加說明

關於特定性的一些注意事項

  1. 特定性僅在同一級聯層或來源中的多個宣告針對同一個元素時才適用。特定性僅適用於相同重要性和相同來源和級聯層的宣告。如果匹配的選擇器位於不同的來源,則級聯決定哪個宣告具有優先順序。
  2. 當同一級聯層和來源中的兩個選擇器具有相同的特定性時,會計算作用域鄰近性;具有最低作用域鄰近性的規則集獲勝。有關更多詳細資訊和示例,請參閱如何解決@scope衝突
  3. 如果作用域鄰近性對於兩個選擇器也相同,則源順序就會起作用。當所有其他條件都相等時,最後一個選擇器獲勝。
  4. 根據 CSS 規則,直接目標元素始終優先於元素從其祖先繼承的規則。
  5. 文件樹中元素的鄰近性對特定性沒有影響。

規範

規範
選擇器級別 4
# specificity-rules

另請參閱