瞭解如何使用 @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
也接受了決定下限的範圍限制。
@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 (.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>
元素的父項元素。
Cascade 中的 @scope
在 CSS Cascade 中,@scope
還會加入新條件:指定鄰近區域。步驟必須具有明確性,但在出現順序之前。
如根據規格:
比較在樣式規則中出現的不同範圍根層級宣告的宣告時,則在範圍 Root 與限定範圍樣式規則之間,宣告的代別或同層元素躍點最少就會獲勝。
這個新步驟可在建立元件的多個變化版本巢狀結構時派上用場。以這個範例來說,還沒使用 @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 上提供)