BLOG

【CSS】セレクタの範囲を指定する@scope を使ってみた

Written by koizumi

INDEX

今回はGoogle Chrome 118 からサポートされたCSSの@scopeの使い方を紹介します。

こちらを使うとセレクタの適用範囲を簡単に指定することができます。

なお、執筆時点ではChromeとEdgeでのみサポートされています。

@scopeとは

cssの@scopeを使用すると、セレクタの範囲を設定できます。

範囲を設定するには、対象とするサブツリーの上限を決めるスコープルート(scoping root)を設定します。

スコープルートを設定すると、その中に含まれるスタイル(スコープ付きスタイルルール)はスコープルートの子孫要素に対してのみ適用されます。

HTML

<body>
  <div class="top">
    <p>top</p>
  </div>
  <div class="center">
    <p>center</p>
  </div>
  <div class="bottom">
    <p>bottom</p>
  </div>
</body>

CSS

@scope(.center) {
   p {
    color: red;
   }
}

表示

上記のコードでは、.contentでスコープされた定義の中にpがセレクタとして用いられています。よって、これらのセレクタはpなら何でもマッチするわけではなく、.contentの子孫であるpにのみマッチします。

@scopeの下限を設定する

@scopeは、スコープの下限を設定できます。スコープの下限を設定するには、to構文を用います。

HTML

<main>
 <div class="header">
  <h2 class="title">header</h2>
  <p class="text">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
 </div>
 <div class="content">
  <h2 class="title">content</h2>
  <p class="text">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
  <div class="about">
   <h3 class="title">about</h3>
   <p class="text">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
  </div>
 </div>
</main>

 CSS

/* 一部省略 */

@scope(.content) to (.about) {
  .title {
    text-align: center;
  }
  .text {
    color: red;
  }
}

表示

このスコープ付きスタイルルールは、.contents要素の内側であり、かつ.aboutの外側にある要素にのみ適用されます。

上限と下限を持つこの形式のスコープは、ドーナツスコープと呼ばれます。

:scopeセレクタ

デフォルトでは、全てのスコープ付きスタイルルールは、スコープルートからの相対パスとなります。

スコープルート自体をターゲットとする場合は:scopeセレクタを利用します。

@scope (.content) {
    :scope {
        /* .content全体にマッチ */
    }
    p{
       /* .content内の<p>にマッチ */
    }
}

実際は、スコープ付きスタイルルールのセレクタには全て:scopeが暗黙的に先頭に付与されています。

必要であれば明示することもでき、CSSのネストを使用して&セレクタを先頭に追加することもできます。

@scope (.content) {
  p {
    /* .content内の<p>にマッチ */
  }
  :scope p {
    /* 上記と同じ */
  }
  & p {
    /* 上記と同じ */
  }
}

スコープリミットに:scope疑似クラスを使い、スコープルートとの関係を記述することもできます。

/* main直下の.contentまでが範囲 */
@scope (main) to (:scope > .content) { ... }

さらにスコープ外の要素を参照することもできます。

/* .asideの中にある場合のみ下限が設定される */
@scope (.content) to (.aside :scope .about) { ... }

ただし、スコープの外に出ていくことはできません。

:scope + pのような記述は、スコープ内に無い要素を選択しようとするため無効です。

スコープ近接度

@scopeの導入に伴って、新たな基準『スコープ近接度』が導入されました。

異なるスコープルートを持つ複数のスタイルルールが重なった場合、スコープ内で宣言した要素と距離が一番近いスコープルートの宣言が優先されます。

HTML

<body>
 <main>
  <div>
   <p>what's color?</p>
  </div>
 </main>
</body>

CSS

@scope (main) {
 p { color: red; }
}

@scope (body) {
 p { color: blue; }
}

上記のコードでは「mainのスコープの中のp」と「bodyのスコープの中のp」に対して異なるスタイルを当てており、p要素は2つのスタイル指定の両方を満たしています。

今回の場合はpmainの距離が2、pbodyの距離が3となるので、より距離の近いスコープルートmainのスタイルが適用されます。

ちなみに、スコープに属さないセレクタとスコープに属すセレクタをスコープ近接度で比較した場合、スコープに属すセレクタのほうが優先されます。

@scope (main) {
 p { color: red; }
  /* こちらが優先 */
}

main p { color: blue; }

注意点

注意点として、@scopeはセレクタの範囲を制限するものであり、スタイルの分離を提供するものではありません。子プロパティに継承されるプロパティは、@scopeの下限を超えても継承されます。

たとえば下記のcolorプロパティは、ドーナツスコープの中にも継承されます。

CSS

@scope(.content) to (.about) {
  :scope {
    color: red;
  }
}

表示

終わりに

今回は@scopeの基本的な使い方を紹介いたしました。

同じクラス名をつけた要素の1箇所だけスタイルを変えたい時など、今まではBEM記法を使って対応していましたが、@scopeを使うことでもっと簡単に対応できるようになりそうです。

執筆時点では対応ブラウザはChromeとEdgeのみとなっているため、使用する機会は少ないと思われますが、今後他のモダンブラウザでサポートされた際に使いこなせるよう覚えておくとよいでしょう。

参考

https://developer.chrome.com/docs/css-ui/at-scope?hl=ja

https://qiita.com/rana_kualu/items/22546111bb12e56b1304

https://zenn.dev/uhyo/articles/css-cascading-6-scope