@scopeとは
以下のコードのようにマッチするセレクタを絞った指定ができる機能のこと↓
<div class="scope-root">
<p>@scopeはセレクタの有効範囲を指定できる</p>
</div>
<p>私は有効範囲ではないです</p>
@scope (.scope-root) {
/* 「:where(.scope-root) p」と同じ*/
p {
color:red;
}
}
@scopeの書き方
以下を解説↓
- 構文
- 一般的な書き方
- スコープリミットを指定した書き方
- セレクタを省略した書き方
- スコープを入れ子にした書き方
構文
@scope [(<scope-start>)]? [to (<scope-end>)]? {
<rule-list>
}
/*例*/
@scope (.scope-root) to (.scope-limit) {
p {
color:red;
}
span {
font-size:1000rem;
}
}
<scope-start>と<scope-end>にはセレクタを記述する。前者はスコープルート(scoping-root)を、後者はスコープリミット(scope-limit)を特定するために使われる。
スコープルートやスコープリミットには様々なセレクタの指定ができる。例えば、子結合子(.parent > .child
)や属性セレクタ([data-scope]
)など。
<rule-list>とは、ルールセットのリストのこと。ルールセットとは、セレクタ(例:pや#rootなど)と宣言ブロック(例:{ color:red;}
)のまとまりのこと。
角括弧([])はグループ分けのために使われる。
クエスチョンマーク(?)は、直前の単語、型、グループが省略可能であることを表す。つまり、@scopeではセレクタを省略できる。
擬似要素(::after、::beforeなど)は<scope-start>、<scope-end>どちらにも使用できないため注意する。
一般的な書き方
@scopeに続く()内のセレクタのことをスコープルート(scoping root)と呼ぶ。
スコープルートとは、「この要素の子孫からセレクタの適用が始まります」ということを明示的にする。
スコープルートに指定された要素は有効範囲に含まれず、スコープルートの子孫要素が含まれる。
<div class="hoge">
<a href="#">スコープにマッチした要素</a>
</div>
<a href="#">スコープにマッチしない要素</a>
@scope (.hoge) {
/*.hogeの子孫のa要素が対象。*/
a {
color:red;
}
/*以下の指定は、適用されない。これは、「.hogeの子孫の.hoge要素に適用される」*/
.hoge {
background-color:;
}
}
スコープリミットを指定した書き方
スコープリミットはセレクタ適用の除外範囲を設定する。
スコープリミットを指定するときは、スコープルートに続いて「to」キーワードの後にセレクタを指定する。
スコープルートと同じく、スコープリミットに指定された要素は有効範囲には含まれない。
<div class="scoping-root">
<a href="#">スコープにマッチする要素</a>
<div class="scoping-limit">
<a href="#">スコープにマッチしない要素</a>
</div>
<a href="#">スコープにマッチする要素</a>
</div>
@scope (.scoping-root) to (.scoping-limit) {
/*.scoping-rootの子孫要素かつ.scoping-limitの子孫要素でないa要素が対象*/
a {
color:red;
}
}
スコープの有効範囲は、「スコープルートの子孫要素かつ、スコープリミットの子孫要素でない」となる。
今のところ、スコープルートとスコープリミットに選ばれた要素は、対象から除外される。
セレクタを省略した書き方
CSSファイルでは無意味だったが、htmlファイルのstyle要素内でセレクタを省略した場合、親要素がスコープルートになる。
<div id="foo">
<style>
@scope {
p { color: red; }
}
</style>
<p>this is red</p>
</div>
<p>not red</p>
<!-- 仕様書からコードを引用 -->
上記の場合、id属性fooのdiv要素がスコープルートになる。
スコープを入れ子にした書き方
@scopeは入れ子にすることができる。
<div class="scoping-root">
<div class="scoping-limit">
<p>入れ子にした</p>
</div>
<p>私はマッチされません</p>
</div>
@scope (.scoping-root) {
@scope(.scoping-limit) {
/*.scoping-rootの子孫要素かつ、.scoping-limitの子孫要素のp要素 */
p {
color: red;
}
}
}
@scopeで使える特別なセレクタ
スコープ内で使える二つの特別なセレクタが存在する↓
:scope
セレクタ&
セレクタ
上記のセレクタ達について解説していく。
:scopeセレクタ
元から存在するCSSの擬似クラスで、セレクターが選択する対象の参照点である要素を表す。これまでは、スコープ付きの要素を特定する方法がなく:rootを表していた。
スコープ内で:scopeを使用すると、スコープルートに指定した要素自身を選択する。
<div class="scoping-root">
スコープルート自身を選択する
</div>
@scope (.scoping-root) {
:scope {
background-color: pink;
}
}
スコープルートに:scopeを指定すると、html要素が選択される↓
@scope(:scope) {
/*html要素がスコープルート*/
}
スコープリミットに:scopeを指定すると、スコープルートの要素が選択される↓
@scope (.scoping-root) to (:scope > p) {
/*.scoping-rootの子孫要素かつ、.scoping-root直下のp要素の子孫要素でない範囲が対象*/
}
&セレクタ
通常のCSSの入れ子でも使えるセレクタで、スコープ内で使用するとスコープルートを表すセレクタを表す。
二つのセレクタの違い
仕様書にはマッチングにおける違いと、詳細度における違いが記載されていた。
マッチングにおける違い
Chromeの記事から引用(原文をDeepL翻訳)↓
specificityの計算方法の違いの他に、:scopeと&のもう一つの違いは、:scopeがマッチしたスコープ・ルートを表すのに対し、&はスコープ・ルートとマッチするために使われたセレクタを表すことである。
このため、&を複数回使うことができる。スコープ・ルートの中にスコープ・ルートをマッチさせることはできないので、一度しか使えない :scope とは対照的です。
Limit the reach of your selectors with the CSS@scope
at-rule
つまり、:scopeセレクタは以下のような指定ができない↓
@scope (.root) {
/*スコープ内では:scopeセレクタは一度しか使えない*/
:scope :scope {
color:red;
}
}
&セレクタはスコープ内で複数使えるため、「.root要素内の.root要素」みたいなセレクタをマッチできる↓
@scope (.root) {
& & {
color:red;
}
}
サンプルで確認↓
詳細度における違い
- :scopeセレクタの詳細度は擬似クラスと同等になる。
- &セレクタの詳細度は、セレクタの中で最も詳細度の高いセレクタと同等になる。
<p id="apple">
りんご
</p>
<p class="banana">
バナナ
</p>
@scope (#apple,.banana) {
& {
/*詳細度は一番高い#appleに合わせて(1,0,0)になる*/
background-color:pink;
}
:scope {
/*詳細度は擬似クラスに合わせて(0,1,0)になる*/
background-color:red;
}
}
/*よって背景色はピンクになる*/
@scopeと詳細度
以下の場合でどのルールセットが優先されるかについて解説する↓
- 複数のスコープルートで同じ要素に宣言した場合
- スコープの内外で同じ要素に宣言した場合
複数のスコープルートで同じ要素に宣言した場合
この場合、スコープ内で宣言した要素と距離が一番近いスコープルートの宣言が優先される。
これをスコープの近接性(scope proximity)と呼ぶ。
コードで確認↓
<div class="red">
<div class="green">
<div class="blue">
<p>これは何色になる?</p>
</div>
</div>
</div>
@scope (.blue) {
/*p要素から見て一番違いスコープルートは.blueのdiv要素のため青色になる*/
p {
color:blue;
}
}
@scope (.red) {
p {
color:red;
}
}
@scope (.green) {
p {
color:green;
}
}
ただし、他のスコープルート内の宣言の詳細度が高い場合、それが優先される。
スコープの内外で同じ要素に宣言した場合
スコープの内外とは、スコープに属してるかそうでないかのこと。
詳細度が同じ場合は、スコープに属しているルールセットが優先される。
<div class="root">
<p>これは赤色</p>
</div>
@scope (.root) {
p {
color: red;
}
}
p {
color: blue;
}
@scopeの便利な使い方
便利な使い方については、仕様書またはuhyoさんのZenn記事を参照↓
注意点
scopeはセレクタの範囲を制限するための機能で、スタイルを分離するための機能ではない。
そのため親要素から子要素に継承されるプロパティは、スコープリミットを設けていてもその子孫要素に影響を与える。(例えば、colorプロパティ)
詳しくは以下の記事を確認↓
また、&セレクタを入れ子にしたスコープ内で使う場合、マッチングされる要素が異なるため注意が必要↓
<p class="scoping-root">
<span>りんご</span>
</p>
@scope (.scoping-root) {
@scope (span) {
/*「scoping-rootの子孫要素かつspanの子孫要素のspan要素」となり、上記のHTMLではマッチングする要素はない。つまり、colorプロパティは効かない*/
& {
color:red;
}
}
}
ブラウザ別対応状況
記事執筆時点(2023.10.3)でChrome118のみ対応↓