@scopeでCSSの詳細度を統一する

これまでセレクターの適用範囲を絞るためにはCSSの詳細度を高めることで実現してきたと思います。
詳細度を高めずにセレクターの適用範囲を設定できる`@scope`についてまとめます。


## `@scope`のブラウザサポート状況
2023年11月11日時点でのブラウザサポート状況です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/fac6adc8-6e95-bf6f-c71d-8beb4f192447.png)

Chromeの118からサポートされた`@scope`は、SafariやFirefoxでは利用できないようなので、気にせず利用できるようになるまでにはもう少しかかりそうです。

## `@scope`の使い方
使い方は簡単です。
`.hoge`の子要素にあるspanタグのテキストを赤色にしたい場合は下記のように書きます。

```scss
@scope (.hoge) {
  span {
    color: red;
  }
}
```

## `@scope`の利点
はじめに記述した通り、CSSの詳細度をあげることなくスタイルを当てることができます。

```scss
// 従来の指定:詳細度（0.1.1）
.hoge {
  span {
    color: red;
  }
}

// @scopeを使った場合：詳細度（0.0.1）
@scope (.hoge) {
  span {
    color: red;
  }
}
```

`@scope`で使用したセレクターは詳細度に影響を与えることはありません。
なので`@scope`を使って指定したspanタグは下記のclassでも上書きできます！

```scss
.blue {
  color: blue;
}
```

## ドーナッツスコープ
初めて聞きましたが、指定した要素の間に設置された要素に対してスタイルを適用する方法のことをドーナッツスコープといいます！（知らんかった…）

```scss
@scope (.hoge) to (.hoge-inner) {
  span {
    color: red;
  }
}
```

## `:scope`擬似クラスについて
擬似クラスを利用することでスコープの対象にしたセレクターに対してスタイルを当てることが可能です！

```scss
@scope (.hoge) {
  :scope {
    // .hogeに対してスタイルを当てられる
  }
}
```

また、対象にした子要素に対してスタイルを当てたい時はいくつか方法があります！

```scss
@scope (.hoge) {
  // ①
  span {
    color: red;
  }

  :scope span {
    // .hogeの子要素であるspanにスタイルを当てる「①」と同じ
  }

  & span {
    // .hogeの子要素であるspanにスタイルを当てる「①」と同じ
  }
}
```

複雑な要件も実現できます！

```scss
// :scope直下の子要素である場合に適応させたい場合
@scope (.hoge) to (:scope > .hogehoge) {}

// .hogehoge内に:scopeがある場合に適応させたい場合
@scope (.hoge) to (.hogehoge :scope .hogehoge-inner) {}
```

ただ、下記のような記述はスコープ内にない要素を指定することになるので無効となります…
```scss
@scope (.hoge) {
  :scope + span {
  
  }
}
```

## `@scope`の詳細度
`@scope`の中で擬似クラスを利用すると詳細度が上がってしまうことは留意しておいてください！

```scss
@scope (.hoge) {
  span {
    // 詳細度：（0, 0, 1）
  }
  
  :scope span {
    // 詳細度：（0, 1, 1）
  }
  
  & span {
    /*
      「&」は「is()」糖衣されます
      実際には、:is(.hoge) spanとなります
      詳細度：（0, 1, 1）
    */
  }
}
```

:::note warn
`is()`の詳細度の計算には注意が必要です。
`is()`の詳細度は囲まれたセレクターの中で最も高い詳細度のものが計算の対象となります。

```scss
is(#hoge, .hoge) span {}
```
上記の場合詳細度は、`is()`の中で最も詳細度が高いのは`#hoge`なので詳細度は(1,0,0)となり、
詳細度の合計はその後の`span`と合わせて(1,0,1)となります。
:::

:::note info
糖衣されるとは？

https://e-words.jp/w/%E3%82%B7%E3%83%B3%E3%82%BF%E3%83%83%E3%82%AF%E3%82%B9%E3%82%B7%E3%83%A5%E3%82%AC%E3%83%BC.html

:::

## プレリュードなしのスコープ
プレリュードとは`@scope`の後につく`()`で囲んだ部分のことです。
下記の記述でいうと「.hoge」の部分にあたる箇所です。

```scss
@scope (.hoge) {}
```

プレリュードは`<style>`タグを利用してHTMLに直接書くことで省略することができます。

```html
<div class="hoge">
  <div class="hogehoge">
    <style>
      @scope {
        color: red;
      }
    </style>
  </div>
</div>
```

## スコープ近接性
CSSのカスケードに新しいステップが追加されました。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/51da7006-94c5-d727-9605-aa5faf427be7.png)
https://developer.chrome.com/articles/at-scope/

これによって同名のclassを入れ子にする場合の制御が便利になりました。

```html
<style>
  .red { color: red; }
  .blue { color: blue; }
  .red span { color: red; }
  .blue span { color: blue; }
</style>
<div class="red">
  <span>hoge</span>
  <div class="blue">
    <span>hoge2</span>
    <div class="red">
      <span>hoge3</span>
    </div>
  </div>
</div>
```

上記のスタイリングの結果は以下の画像のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/1a771a9b-371f-c622-46ce-bb49ee44e7db.png)

本来であれば「hoge3」は赤文字になってほしいところですが、カスケードの特性上`.blue span`が一番優先されます。

`@scope`だと下記のように記述することで上記の問題を解決できます！
```html
<style>
  @scope (.red) {
    :scope { color: red; }
    span { color: red; }
  }
   @scope (.blue) {
    :scope { color: blue; }
    span { color: blue; }
  }
</style>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/412f5217-f1e3-627b-16d4-fe3a676b1868.png)

## まとめ
カスケードの仕組み上、外部スタイルシートの読み込み順や書き順を意識しないといけない場合もありましたが`@scope`を利用することでこれまで必要だった思考が必要なくなりそうです！

とはいえまだ最新のブラウザでしか利用できないのでしばらく利用は控えた方が良さそうな印象です。
そもそもReactやVue.jsのようなフレームワークを利用すれば`@scope`を使わなくてもスコープを切ってスタイリングができるので実装技術を検討するのも方法かと思います！

プレーンなCSSでスタイルの定義が分離できるのはコードの見通しが良くなり修正のしやすさもUPできるのは良いですね！

## 参考リンク
https://developer.chrome.com/articles/at-scope/