iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0
Modern Web

前端迷走中:從零開始的新手之路系列 第 24

[Day 24] 選擇不障礙,讓CSS幫你──Specificity決定選擇器優先順序

  • 分享至 

  • xImage
  •  

前幾天我們介紹了不同類型的選擇器。

包括元素、類別、ID、通用等基本的選擇器,
依任意屬性挑選元素的屬性選擇器,
依不存在的分類挑選元素的偽類選擇器,
依不存在的元素挑選內容的偽元素選擇器。

也提到怎麼將這些選擇器組合在一起使用。

不過了解這些之後,還需要了解選擇器之間的優先順序。

不同選擇器之間的優先順序,取決於他們的Specificity(這裡譯作具體性)。

具體性Specificity

當檔案內透過多個選擇器宣告的樣式彼此衝突時,會比較這些選擇器的具體性,再決定要套用哪一個選擇器的樣式。

選擇器的具體性會根據選擇器類型對應的權重做計算。

權重總共分成三種等級:

  • A級
    ID選擇器
  • B級
    類別選擇器、屬性選擇器、偽類選擇器(有些例外)
  • C級
    元素選擇器、偽元素選擇器

需要注意的是:[1]

  • 通用選擇器跟組合器不會影響具體性的值,在計算過程中會被忽略。
  • 以屬性選擇器去挑選特定ID的元素時,權重會是屬性選擇器的B級,而非ID選擇器的A級。

具體性的計算

計算具體性時,就是在算每種權重的選擇器有多少個。

可以將每種權重的選擇器的總數列成數列,以A級, B級, C級的形式呈現每個選擇器的具體性。

所以上面三種權重的選擇器,具體性可以寫成:

  • A級:1,0,0
  • B級:0,1,0
  • C級:0,0,1

由於選擇器也可以組合起來變成新的選擇器,這類由多個選擇器組成的選擇器(複合選擇器、複雜選擇器)計算具體性時,會將其內包含的選擇器對應的具體性相加。

選擇器列表的具體性則會依當下符合的條件而定。先一一計算列表中每個選擇器的具體性,接著從符合條件的選擇器中找出最高的值,作為列表的具體性。

具體性的比較

當元素的屬性同時經檔案內的多個規則設定為不同的值,需要比較選擇器的具體性,由具體性比較高的規則決定屬性最終的值。

比較選擇器的具體性時會由左至右,依序比較A級、B級跟C級權重選擇器的數量。

就像是玩大老二時會先比撲克牌的數字,當數字相同時再比花色。

如果在前一級就已經分出勝負,不會繼續往下比其他級的權重選擇器有多少;
只有前一級的權重選擇器的數量相同時,才會繼續往下比。

如果選擇器的具體性一樣,則會繼續比較元素與規則間的scoping proximity(作用域鄰近性);
如果還是比不出來,則會比較規則出現於檔案的先後順序。[1]

關於scoping proximity,之後會再提,這裡先帶過~

範例

以下舉例說明怎麼計算選擇器的具體性:

  • * 0,0,0
    忽略通用選擇器。

  • ul ol+li 0,0,3
    C級權重有3個:3個元素選擇器;
    忽略組合器。

  • h1 + *[rel="up"] 0,1,1
    B級權重有1個:1個屬性選擇器;
    C級權重有1個:1個元素選擇器。
    忽略通用選擇器跟組合器。

  • ul ol li.red 0,1,3
    B級權重有1個:1個類別選擇器;
    C級權重有3個:3個元素選擇器。

  • [id="password"] 0,1,0
    B級權重有1個:1個屬性選擇器。
    (雖然是id屬性,但不是用ID選擇器)

  • input:focus 0,1,1
    B級權重有1個:1個偽類選擇器;
    C級權重有1個:1個元素選擇器。

  • :root #myApp input:required 1,2,1
    A級權重有1個:1個ID選擇器;
    B級權重有2個:2個偽類選擇器;
    C級權重有1個:1個元素選擇器。

計算具體性時的特殊情況

:where()

:where()被稱作具體性調整偽類(specificity-adjustment pseudo-class),會將括號內的選擇器的具體性替換為0。[2]

例如以下由規範提供的例子中,選擇器的具體性是0,1,0。
因為括號內的選擇器的具體性會變成0,0,0,加上:where()前面.qux的具體性0,1,0,結果會是0,1,0。

.qux:where(em, #foo#bar#baz)

:nth-child():nth-last-child()

如果:nth-child():nth-last-child()的括號內放入了選擇器列表,則具體性會是偽類本身的值,再加上列表中具體性最高的選擇器的值。[2]

例如以下由規範提供的例子中,選擇器的具體性是0,2,0。

因為列表中,具體性最高的選擇器是類別選擇器.item,值為0,1,0。加上:nth-child偽類本身的具體性0,1,0,最後會是0,2,0。

:nth-child(even of li, .item) 

:is():has():not()

:is():has():not()本身的具體性,會被括號內的選擇器取代。[2]

例如以下由MDN提供的例子:

  • :is(p) 0,0,1
    :is()的具體性由p取代,而p的具體性是0,0,1。

  • h2:has(~ h2) 0,0,2
    :has()的具體性由~ h2取代,~ h2的是0,0,1;
    加上前面h2的0,0,1,結果會是0,0,2。

  • div:not(.inner) p 0,1,2
    :not()的具體性由.inner取代,.inner的是0,1,0;
    加上前面div的0,1,0,以及後面p的0,0,1,結果會是0,1,2。

如果括號內是選擇器列表,:is():has():not()本身的具體性會由列表中具體性最高的選擇器取代。[2]

例如以下由MDN提供的例子:

  • :is(p, #fakeId)
    :is()的具體性由值較高的#fakeId取代,結果會是#fakeId的1,0,0。

  • h1:has(+ h2, > #fakeId)
    :has()的具體性由值較高的#fakeId取代,#fakeId的具體性是1,0,0;
    加上前面h1的0,0,1,結果會是1,0,1。

  • p:not(#fakeId)
    :not()的具體性由值較高的#fakeId取代,#fakeId的具體性是1,0,0;
    加上前面p的0,0,1,結果會是1,0,1。

  • div:not(.inner, #fakeId) p
    :not()的具體性由值較高的#fakeId取代,#fakeId的具體性是1,0,0;
    加上前面div的0,0,1,後面p的0,0,1,結果會是1,0,2。

巢狀結構的規則

在CSS中,可以將規則以多層的巢狀結構改寫,讓程式碼更簡潔。[3]

使用選擇器列表組成巢狀結構的規則,類似於使用:is()。計算整個選擇器的具體性時,會由列表中具體性最高的選擇器,得出選擇器列表的具體性。[1]

例如以下由MDN提供的例子,選擇器列表p, #fakeId的具體性,會是#fakeId的1,0,0,而非p的0,0,1。

再加上span的0,0,1,會得出整個選擇器的具體性為1,0,1。所以由這個巢狀規則拆出的兩個規則的選擇器,p span#fakeId span,具體性都會是1,0,1。

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

減少具體性的方法

當多個規則設定的樣式衝突時,可以減少其他規則的選擇器的具體性。這麼一來,就相對提高想要套用的規則的優先順序。

改用屬性選擇器處理id

前面提過,如果以屬性選擇器去挑選特定ID的元素時,權重會是屬性選擇器的B級,而非ID選擇器的A級。

運用這個特性,以屬性選擇器替換原先的ID選擇器,就能降低選擇器的具體性了。

使用:where()

前面也提過,:where()會把括號內的選擇器的具體性歸零。

這麼一來,即使不得不把選擇器的條件寫得更明確,也不會增加選擇器的具體性。

增加具體性的方法

如果無法減少其他規則的具體性,也可以想辦法提高想要套用的規則的優先順序。

以組合器加入元素的上層元素

一個增加選擇器具體性的方法是,在原先的選擇器前,以後代或子代組合器,加入元素的祖代或親代元素。

例如以下由MDN提供的例子中,在原本的元素選擇器h1前面,另外加了選擇上層元素的選擇器,並透過後代組合器相接。

如果是加了ID選擇器#myContent,選擇器的具體性會由原先的0,0,1提升為1,0,1;
如果是加了屬性選擇器[id="myContent"],則會提升為0,1,1。

#myContent h1 {
  color: green; /* 1-0-1 */
}
[id="myContent"] h1 {
  color: yellow; /* 0-1-1 */

重複同樣的選擇器

如果不得已,需要蓋過其他具體性非常高的選擇器時,也可以讓原先就有的A級或B級權重選擇器重複好幾次,快速提高選擇器的具體性。

例如以下由MDN提供的例子中,將#myId span的ID選擇器#myId重複兩次,變成#myId#myId#myId span,讓選擇器的具體性增加2,0,0,變成3,0,1;
如果是將.myClass span的類別選器重複兩次,則會讓選擇器的具體性增加0,2,0,變成0,3,1。

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

這個方法也可以透過:is():has():not()等偽類來達成。如此,即便無法對上層元素添加id屬性,也可快速提高選擇器的具體性。

例如以下由MDN提供的例子,將任意的ID選擇器#fakeID重複相接,帶入:not():is()裡,分別將選擇器的具體性由原先的0,0,1,提升為3,0,1跟3,0,0。

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

不過重複同樣的選擇器並不是好的做法。
如果可以,應該優先減少其他選擇器的具體性。
如果無法減少其他選擇器的具體性,不得不這麼做的時候,也應該以註解說明這麼做的理由。[1]

小結

今天提到選擇器的優先順序,由它的具體性Specificity決定。

計算選擇器的具體性時,會依據對應的權重將選擇器分類,算出每種權重的選擇器有多少個。

比較選擇器的具體性以決定優先順序時,會先比較最高權重的選擇器總數;
分不出勝負時,會由高到低,繼續往下比較其他權重的選擇器總數。

不過,在比較選擇器之前,其實需要先考慮規則來自哪個樣式表,或是有沒有加上!important等條件。

只有當衝突的規則有同樣的來源、重要性時,才會比較選擇器的具體性。

不過時間不多了,讓我們下集待續吧~

參考資料

[1]: MDN, Specificity
[2]: W3C, Selectors Level 4, specificity-rules
[3]: MDN, Using CSS nesting


上一篇
[Day 23] 選擇不障礙,讓CSS幫你──偽元素選擇器
系列文
前端迷走中:從零開始的新手之路24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言