前兩天,我們介紹了Cascading在決定最終套用的樣式時,會優先考量的四個因素。
包括來源跟重要性、contxt、行内樣式跟分層。
今天要繼續說明剩下的三個因素,具體度、範疇鄰近度、出現順序。
如果無法以第四個因素決定最終採用的樣式,就會繼續考量第五個因素Specificity,比較規則使用的選擇器的具體度。
關於具體度怎麼計算與比較,之前我們有講解過了,這邊做個簡單回顧。
ID選擇器的權重最高,具體度為1,0,0;
類別選擇器、屬性選擇器、偽類選擇器(有些例外)的權重次之,具體度為0,1,0;
元素選擇器、偽元素選擇器的權重最低,具體度為0,0,1。
需要注意的是:
:where()
會將括號內選擇器的具體度歸為0。:is()
、:has()
、:not()
的具體度,跟括號內的選擇器一樣。如果是選擇器列表,同樣會依當下情況選出最高的。:nth-child()
、:nth-last-child()
的具體度會是本身偽類的0,1,0,加上括號內選擇器的具體度。is()
。如果有選擇器列表,同樣會依當下情況選出最高的。計算出具體度後,會從最左邊的數字開始比大小;
如果數字相同,再由左至右繼續往下比。
如果宣告的具體度相同,無法分出勝負,就會繼續考量第六個因素Scope Proximity。
CSS Cascading and Inheritance Level 6(目前仍是Working Draft)開始,CSS多了@scope的語法。將規則包在@scope的區塊裡面,可以設定規則在文件中的適用範疇。
於是Cascading的過程中,多考量了宣告的Scope Proximity(這裡譯作範疇鄰近度),判斷規則的範疇的根元素,與符合規則條件的元素之間的距離。
@scope
簡介@scope後面的括號,表示範疇的上限跟下限。括號內填入選擇器,可以是選擇器列表,但不能是偽元素選擇器;如果有設定下限,上限跟下限的括號中間要加上關鍵字to
。
例如以下由MDN的例子,以類別選擇器.article-body
設定上限,以元素選擇器figure
設定下限。
@scope (.article-body) to (figure) {
img {
border: 5px solid black;
background-color: goldenrod;
}
}
由@scope設定的文件範疇,以符合上限條件的元素作為範疇的根元素(scope root)。如果沒有設定下限條件,範疇會涵蓋根元素的所有後代元素。
如果有設定下限,範疇只會涵蓋到下限的親代元素,不會包含符合下限條件的元素。但可以透過選擇器跟組合器來調整範疇是否包含上限跟下限。
像是在下限的選擇器後面加上 > *
,會讓範疇涵蓋到符合下限條件的元素;
假如是在上限的選擇器後面加上 > *
,範疇的根元素則會變成原本根元素的子代元素,就沒有涵蓋上限了。
:scope
偽類範疇的根元素可以用:scope
偽類來表示。
設定下限條件時,能用:scope
進一步指定符合條件的元素與根元素之間需要有什麼關係。
延續前面的例子。如果下限加上:scope
與子代組合器,則符合下限條件的元素就需要是根元素的子元素。
@scope (.article-body) to (:scope > figure) {
/* … */
}
@scope
區塊內的規則@scope區塊內的規則,可以用:scope
作為選擇器,對範疇的根元素宣告樣式。可以參考MDN的例子:
@scope (.feature) {
:scope {
background: rebeccapurple;
color: antiquewhite;
font-family: sans-serif;
}
}
除了:scope
,@scope區塊內的規則也可以使用其他選擇器。
這些選擇器挑選出的元素,預設都會是根元素的後代元素,就好像這些選擇器是以後代組合器接在:scope
後面。
如果想要調整這些元素與根元素之間的關係,可以在這些選擇器前面加上其他組合器,像是子代組合器。
以下由規範提供的例子,便透過子代組合器,限定宣告的樣式只會套用到根元素的子元素;
如同前面說的,兩個規則的選擇器> p
跟:scope > p
相等,會選到相同的元素。
@scope (#my-component) {
> p { color: green; }
:scope > p { color: green; }
}
上、下限括號內的選擇器,不會影響規則的選擇器具體度。只會考慮@scope的規則,用了哪些選擇器。
如果區塊內的規則有使用:scope
,就會加上:scope
的具體度0,1,0,跟一般的偽類一樣。
當衝突的宣告來自適用範疇不同的規則時,會比較規則適用的元素,與規則範疇的根元素之間的距離,看哪個比較近。
這個距離取決於他們在DOM樹狀模型中的層級關係。包括彼此中間隔了幾層,還有同一層中隔了幾個元素。
如果規則沒有設定適用的範疇,則這個距離會被視為無限大。所以優先順序會比,有設定範疇的規則還低。
假如範疇鄰近度相同,無法分出優先順序,就會繼續考量第七個因素Order of Appearance。
接下來透過MDN提供的例子,說明實際上要怎麼比較範疇鄰近度。
網頁中有這樣的HTML程式碼:
<div class="light-theme">
<p>Light theme text</p>
<div class="dark-theme">
<p>Dark theme text</p>
<div class="light-theme">
<p>Light theme text</p>
</div>
</div>
</div>
假如搭配以下的CSS程式碼,則最內層的<div>
,背景顏色會設為灰色,因為符合選擇器.light-theme
的條件。
不過這個<div>
裡面的<p>
,會同時符合選擇器.light-theme p
跟.dark-theme p
的條件。
由於兩個選擇器的具體度一樣,且規則沒有設定範疇,接著就會比較他們出現的優先順序,由最晚出現的.dark-theme p
勝出。於是<p>
裡面的文字顏色會變成白色。
但白色的字搭上灰色的底,會看不清楚。原先的規劃是淺色背景搭配黑色字,深色背景搭配白色字才對。
.light-theme {
background: #cccccc;
}
.dark-theme {
background: #333333;
}
.light-theme p {
color: black;
}
.dark-theme p {
color: white;
}
為免原先規劃的樣式因為出現順序失效,可以使用@scope將這些規則依配色分成兩組,設定範疇,分別以class
屬性為.dark-theme
跟.light-theme
的元素作為根元素。
最內層的<div>
跟<p>
,同時處在兩組規則的範疇中,但最終會套用根元素為.light-theme
的那組規則。
因為<div>
、<p>
距離.light-theme
只有0、1層,但距離.dark-theme p
有1、2層。
雖然.light-theme
比較早宣告,但在處理衝突的樣式時會先比較範疇鄰近度。所以最內層的<div>
跟<p>
會優先套用範疇鄰近度比較小的.light-theme
宣告的樣式。
@scope (.light-theme) {
:scope {
background: #cccccc;
}
p {
color: black;
}
}
@scope (.dark-theme) {
:scope {
background: #333333;
}
p {
color: white;
}
}
出現順序Order of Appearance是Cascading最後才會考慮的因素。
當衝突的宣告值無法經由前面幾個因素分出優先順序,就會比較這些宣告出現在文件中的位置。
以最後面出現的宣告作為最終的優勝者,套用其設定的樣式。
直接以元素的style
屬性宣告的行内樣式,位置會在所有樣式表之後。
如果衝突的宣告值都是行内樣式,則會依據元素在文件的位置來判斷先後順序。
<link>
跟<style>
的先後順序不一定透過其他方式宣告的樣式,可能透過<link>
元素連結至文件,也可能出現在文件的<style>
元素中。
不過這兩種元素出現於文件的順序不一定。通常會把<link>
放在<style>
前面,但也可以把<link>
移到<style>
之後。
如果是後者的情況,透過<link>
連結至文件的外部樣式表就會優先於<style>
中宣告的樣式。[1]
<link>
連結的外部樣式表彼此會排序如果衝突的宣告來自不同的外部樣式表,則他們的優先順序同樣會取決於,外部樣式表使用的<link>
元素出現於文件的位置。
所以,越晚連結至文件中的外部樣式表優先順序會越高。如果宣告衝突,就會蓋過前面的外部樣式表的樣式。
<style>
元素內的CSS規則稱作內部樣式表。
在內部樣式表中,可以透過@import
匯入外部樣式表。而這些匯入的外部樣式表中,同樣可以再次透過@import
匯入其他外部樣式表。
不過匯入外部樣式表時,需要把@import
放在其他規則之前,不然會無效;
如果同時匯入多個外部樣式表,這些@import
中間也不能參雜其他規則,不然其他規則後的外部樣式表同樣會無效。[2]
因為必須放在其他規則之前,使用@import
匯入的外部樣式表,就會被後面的內部樣式表蓋過。
@import
並排序使用@import
匯入的外部樣式表,會被視作替換了原先的@import
,就好像外部樣式表內的CSS規則是直接寫在檔案裡面一樣。[2]
每個使用@import
匯入的外部樣式表會被視作獨立的個體。重複匯入同樣的外部樣式表,會被視作不同的樣式表來處理。
如果匯入的外部樣式表彼此宣告的樣式衝突,優先順序會取決@import
出現於文件的位置。因此後面匯入的外部樣式表會優先於前面匯入的。
不過@import
必須寫在其他規則前面。所以無論如何,內部樣式表都會優先於匯入的外部樣式表。
今天我們介紹了Cascading的過程中,會考量的剩下三個因素。
考量第五個因素──具體度時,會比較規則使用的選擇器的具體度。由選擇器具體度最高的規則獲勝。
如果具體度一樣,就會繼續考量第六個因素──範疇鄰近度,由範疇鄰近度最小的規則獲勝。
範疇鄰近度指的是符合規則的元素,與規則範疇的根元素之間,在DOM樹狀模型中的距離。如果規則沒有設定範疇,這個距離會被視為無限大
因此有設定範疇的規則會優先於沒有設定範疇的規則。
如果範疇鄰近度也分不出勝負,就會考慮最後的因素──出現順序。由最晚出現於文件的規則獲勝。
寫在<style>
的內部樣式表跟透過<link>
連結的外部樣式表,彼此優先順序不固定,取決於<link>
跟<style>
在文件中的順序。
如果內部樣式表中使用@import
匯入其他外部樣式表,則內部樣式表會優先於外部樣式表。因為@import
必須寫在其他規則前面才有效。
雖然透過出現順序,Cascading最終能決定要套用什麼樣式。
不過如果這個樣式與上層元素設定的樣式衝突,要怎麼辦?
或是如果根本就沒有對元素宣告樣式,要怎麼呈現才好?
關於這些問題,由於時間不多了,就讓我們下集待續~
[1]: Jessie, Day06 談談CSS重要概念
[2]: W3C, CSS Cascading and Inheritance Level 5, Importing Style Sheets: the @import rule