瞭解如何使用 @scope 只在 DOM 的有限子樹狀結構中選取元素。
撰寫 CSS 選取器的細膩技巧
編寫選取器時,您可能會發現自己陷入兩難的境地。一方面,您必須明確指出要選取哪些元素。另一方面,您希望選取器保持易於覆寫,且與 DOM 結構緊密結合。
舉例來說,如果您想選取「資訊卡元件內容區域中的主圖片」,這是相當明確的元素選取,因此您不太可能會寫入 .card > .content > img.hero
這類的選取器。
- 這個選取器的
(0,3,1)
特異性相當高,因此在程式碼數量增加時,很難覆寫。 - 由於它仰賴直接子項組合器,因此與 DOM 結構緊密結合。如果標記有所變更,您也必須變更 CSS。
不過,請勿只編寫 img
做為該元素的選取器,因為這樣會選取網頁中所有的圖片元素。
要取得適當平衡通常相當困難。部分開發人員多年來不斷想出解決方案和解決方法,協助您解決這類情況。例如:
- BEM 等方法規定您必須為該元素指定
card__img card__img--hero
類別,以便降低特定性,同時讓您選取特定項目。 - 以 JavaScript 為基礎的解決方案 (例如 範圍 CSS 或樣式化元件) 會在選取器中加入隨機產生的字串 (例如
sc-596d7e0e-4
),藉此重新撰寫所有選取器,避免選取器指定網頁另一側的元素。 - 有些程式庫甚至完全廢除選取器,並要求您直接在標記本身中放入樣式觸發事件。
不過,如果完全不需要嗎?如果 CSS 能讓您以非常明確的方式選取元素,而不需要撰寫高度特異的選取器,或是與 DOM 緊密結合的選取器,那麼情況會如何呢?這時就該使用 @scope
了,它可讓您只在 DOM 的子樹狀結構中選取元素。
@scope 隆重推出
如果使用 @scope
,您可以限制選取器的觸及範圍。方法是設定範圍根目錄,這會決定您要指定的子樹狀結構的上限。設定範圍根值後,所包含的樣式規則 (稱為「範圍樣式規則」) 只能從 DOM 的有限子樹狀結構中選取。
舉例來說,如果只要指定 .card
元件中的 <img>
元素,您可以將 .card
設為 @scope
規則的範圍根。
@scope (.card) {
img {
border-color: green;
}
}
範圍樣式規則 img { … }
只會選取與相符 .card
元素「在範圍內」的 <img>
元素。
如要避免選取資訊卡內容區域 (.card__content
) 內的 <img>
元素,您可以讓 img
選取器更具體化。另一種做法是利用 @scope
at-rule 也接受範圍限制的事實,這個限制會決定下限。
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
這個受限樣式規則只會針對祖系樹狀結構中 .card
和 .card__content
元素之間的 <img>
元素,這種有上限和下限的範圍類型通常稱為「圓環範圍」
:scope
選取器
根據預設,所有範圍樣式規則都會相對於範圍根目錄。您也可以指定範圍根元素本身。如要這麼做,請使用 :scope
選取器。
@scope (.card) {
:scope {
/* Selects the matched .card itself */
}
img {
/* Selects img elements that are a child of .card */
}
}
範圍樣式規則中的選取器會隱含地在開頭加上 :scope
。有需要的話,您可以在自己的前方加上 :scope
,清楚表明這件事。您也可以加上 CSS 巢狀結構的 &
選取器。
@scope (.card) {
img {
/* Selects img elements that are a child of .card */
}
:scope img {
/* Also selects img elements that are a child of .card */
}
& img {
/* Also selects img elements that are a child of .card */
}
}
範圍限制可以使用 :scope
虛擬類別,要求範圍根層級與範圍根特定關係:
/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }
範圍限制也可以使用 :scope
參照範圍根以外的元素。例如:
/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }
請注意,範圍樣式規則本身無法逸出子樹狀結構。:scope + p
等選項無效,因為它會嘗試選取不在範圍內的元素。
@scope
和特異性
您在 @scope
前言中使用的選取器不會影響所包含選取器的特定性。在以下範例中,img
選取器的特定性仍為 (0,0,1)
。
@scope (#sidebar) {
img { /* Specificity = (0,0,1) */
…
}
}
:scope
的特殊性是一般虛擬類別的特殊性,也就是 (0,1,0)
。
@scope (#sidebar) {
:scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
…
}
}
在以下範例中,&
會在內部重寫為用於範圍根節點的選取器,並在 :is()
選取器內包裝。最後,瀏覽器會使用 :is(#sidebar, .card) img
做為選取器來進行比對。這項程序稱為「去除糖衣」。
@scope (#sidebar, .card) {
& img { /* desugars to `:is(#sidebar, .card) img` */
…
}
}
由於 &
會使用 :is()
進行去糖化,因此系統會根據 :is()
的特定規則計算 &
的特定性:&
的特定性是其最明確引數的特定性。
套用至這個範例,:is(#sidebar, .card)
的特定性是其最具體引數的特定性,即 #sidebar
,因此會變成 (1,0,0)
。將這項屬性與 img
的特定性 (即 (0,0,1)
) 結合,最後 (1,0,1)
就會成為整個複雜選取器的特定性。
@scope (#sidebar, .card) {
& img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
…
}
}
@scope
內的 :scope
和 &
之間的差異
除了計算特定性的差異之外,:scope
和 &
的另一個差異在於 :scope
代表比對的範圍根,而 &
代表用於比對範圍根的選取器。
因此,您可以多次使用 &
。這與 :scope
相反,因為您只能使用 :scope
一次,因為您無法在範圍根目錄中比對範圍根目錄。
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
無前置範圍
使用 <style>
元素編寫內嵌樣式時,您可以不指定任何範圍根層級,將樣式規則範圍限定為 <style>
元素的封閉父項元素。方法是省略 @scope
的前奏。
<div class="card">
<div class="card__header">
<style>
@scope {
img {
border-color: green;
}
}
</style>
<h1>Card Title</h1>
<img src="…" height="32" class="hero">
</div>
<div class="card__content">
<p><img src="…" height="32"></p>
</div>
</div>
在上述範例中,範圍規則只會將 div
中的元素設為目標,且該元素的類別名稱為 card__header
,因為 div
是 <style>
元素的父項元素。
級聯中的 @scope
在 CSS 層級中,@scope
也新增了一個條件:範圍相近。該步驟會在特定性之後,但在出現順序之前。
如根據規格:
比較樣式規則中顯示的宣告,如果範圍根與範圍樣式規則主體之間的世代或同胞元素跳躍次數最少,則該宣告會勝出。
當您要巢狀化元件的多個變化版本時,這個新步驟就會派上用場。請參考以下尚未使用 @scope
的範例:
<style>
.light { background: #ccc; }
.dark { background: #333; }
.light a { color: black; }
.dark a { color: white; }
</style>
<div class="light">
<p><a href="#">What color am I?</a></p>
<div class="dark">
<p><a href="#">What about me?</a></p>
<div class="light">
<p><a href="#">Am I the same as the first?</a></p>
</div>
</div>
</div>
查看該標記時,第三個連結會是 white
,而非 black
,即使它是已套用 .light
類別的 div
子項也是如此。這是因為階層會根據顯示順序的條件決定勝出者。系統會發現 .dark a
是最後宣告的值,因此會勝過 .light a
規則
有了範圍限制的鄰近性準則,這個問題現已解決:
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
由於兩個已套用範圍的 a
選取器具有相同的特定性,因此範圍相近的條件就會生效。系統會根據兩個選取器與其範圍根源的距離,為兩者評分。對於第三個 a
元素,它只會跳到 .light
範圍根層級,但會跳到 .dark
根層級兩次。因此,.light
中的 a
選取器會勝出。
結論:選取器隔離,而非樣式隔離
請注意,@scope
會限制選取器的觸及範圍,但不會提供樣式隔離功能。沿用至子項的屬性仍會沿用 (超出 @scope
的下限)。color
屬性就是其中之一。在甜甜圈範圍內宣告此值時,color
仍會向下繼承至甜甜圈洞內的子項。
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
在上例中,.card__content
元素及其子項會繼承 .card
的值,因此會顯示 hotpink
顏色。
(封面相片由 rustam burkhanov 在 Unsplash 上提供)