モダンCSSへのアップデート（実践編）

## 目次  

- [はじめに](#はじめに)
- [アップデートによるメリット](#アップデートによるメリット)
- [サンプルページ](#サンプルページ)
- [解説1-スタイルの優先順位を管理](#解説1-スタイルの優先順位を管理)
- [解説2-擬似クラスセレクター](#解説2-擬似クラスセレクター)
- [解説3-上下限つきのレスポンシブ文字サイズ](#解説3-上下限つきのレスポンシブ文字サイズ)
- [解説4-親要素いっぱいまで広げる](#解説4-親要素いっぱいまで広げる)
- [解説5-上下左右中央寄せ](#解説5-上下左右中央寄せ)
- [解説6-親要素を子要素のサイズに合わせて左右中央寄せ](#解説6-親要素を子要素のサイズに合わせて左右中央寄せ)
- [解説7-要素の切り抜き](#解説7-要素の切り抜き)
- [解説8-親行列の行や列に子行列を整列](#解説8-親行列の行や列に子行列を整列)
- [解説9-ピルボタン](#解説9-ピルボタン)
- [解説10-ホバー](#解説10-ホバー) 
- [解説11-親要素サイズでの切り替え](#解説11-親要素サイズでの切り替え)
- [まとめ](#まとめ)
- [参考サイト](#参考サイト)

---

## はじめに
そもそもなぜモダンCSSへのアップデートが必要なのだろうか？
- 学習コストに恩恵が見合わない気がする
- わざわざ [Can I use](https://caniuse.com/) で対応状況を確認する手間をかけてまで使う必要ある？
- 使い慣れた記述のほうが悩まないで済むし安心
- 忙しくて余裕がないから今はまだいい。そのうち、そのうちにね…
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221915/311cd529-22ad-45c0-8b14-4bb0f9374ef3.png" width="300">

---

## アップデートによるメリット
調べたり考えたりしたことをまとめてみました。

1. **可読性・保守性の向上**
・シンプルな記述によりバグ（ミス）の発生率が下がる
2. **開発スピードの向上**
・コードが短縮されればコーディング速度が向上する
3. **パフォーマンスの最適化**
・CSSの進化は「書きやすさ」「便利さ」だけではなく、「パフォーマンスの最適化」にも貢献している（`will-change`、`contain`、`content-visibility`、`font-display: swap;`、 `image-rendering: pixelated;` など）
4. **将来の標準化への適応**
・常に新しい書き方をおぼえておくことで、今後の仕様の変化にもスムーズに対応できる（1から2への移行にそれほどの恩恵がなかったとしても、将来1から3に移行するよりは、2から3への移行のほうが時間も労力も低コストでスムーズ）
5. **モダンなコードの習慣**
・チーム開発において統一感が生まれる
・コードレビューのしやすさ、更新のしやすさ、他の開発者も新記述に慣れやすくなる（教育コストの削減）

なるほど…と納得したところで[こちらのサイト](#参考サイト)を参考に、実際に手を動かして実装しながらアップデートしていこうと思います。

---

## サンプルページ

https://unt-watanabe.github.io/modern_css_sandbox/

- モダンCSSが効果的に使えそうなよくあるUIをマークアップ
- 以下、サンプルページのコードを引用しながら解説

---

## 解説1-スタイルの優先順位を管理
`@layer`

**「カスケードレイヤー」** という機能を使うことで、スタイルの **適用優先順位** をレイヤー（階層）として**明示的** に管理できる
```scss
@layer reset, base, layout, block, unique, utilities;

@layer reset {
...
}

@layer base {
...
}

...
```
＜解説＞
- 今回、バンドラーは簡易的に [Parcel](https://parceljs.org/) を採用したが、Parcel だと SCSS ファイルの先頭に `@use` を書かないと怒られるので `@layer` を下に持ってきた
```scss
@use 'reset';
@use 'base';
@use 'layout';
@use 'block';
@use 'unique';
@use 'utilities';

@layer reset, base, layout, block, unique, utilities;
```
- これも環境によるが `@layer` の中でパーシャルファイルをインポートしようとするとエラーになってしまったので、今回は各パーシャルファイル内に `@layer` を書いて対応した
```_reset.scss
@layer reset {
...
}
```
- 外部ライブラリ（Tailwind CSS、Bootstrap など）と自作の CSS が混在するときでも、ライブラリと自作のレイヤーを分けることで安全に上書きできる
---

## 解説2-擬似クラスセレクター
`:is()`
セレクターのリストを引数に取り、リスト中のセレクターのいずれか一つに当てはまる要素をすべて選択する。詳細度は最も高いセレクタに従う

`:has()`
特定の子要素や兄弟要素を持つかどうかに基づいてその要素を選択する

`:where()`
機能は `:is` と似ているが詳細度は常に0（最弱）
```scss
:is(.bl_header_title, .bl_header_subTitle, .bl_header_sectionTitle) {
  text-wrap: balance;
}

.bl_card_item {
  &:has(.bl_card_button) {
    border: 5px solid var(--color1);
  }
}

// https://github.com/elad2412/the-new-css-reset
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
  all: unset;
  display: revert;
}
// :notが2回に分割されているのは粒度の違いを強調し可読性を高めるため
``` 
＜補足＞
```scss
// text-wrap は要素の中のテキストを折り返す方法を制御するプロパティ
text-wrap: wrap; // テキストは適切な文字で行をまたがって折り返す。既定値
text-wrap: nowrap; // テキストは行をまたがって折り返されない
text-wrap: balance; // テキストは各行の文字数を均等にする方法で折り返す
text-wrap: pretty; // wrap と同様だがパフォーマンスよりも精度優先
```
- Webサイトではよくタイトルが横幅より短めにデザインされるので `balance` は有用であるが、コンピューターに負荷がかかるため限られた行数（Chromium では 6 行以下、Firefox では 10 行以下）のテキストブロックにのみ対応となっている点に注意
```scss
all: unset; // その要素のすべてのプロパティを、既定値が inherit のものは継承値に、そうでなければ初期値に変更する
display: revert; // revert は「ある要素に適用されたスタイルを、その要素が本来持っていたであろうスタイルに戻す」というもの。ここではたとえば「div は inline ではなく block 」というようにデフォルトのdisplay 状態に戻している
```
---

## 解説3-親要素いっぱいまで広げる
`inset`
```scss
.un_mv_inner {
  position: absolute;
  inset: 0;
}
```
＜解説＞
- `inset`は `top` `left` `right` `bottom` を一括指定できるショートハンド
- `inset: 10px;` のように指定すると、親要素に 10px の `padding` があるような見た目になる

---

## 解説4-上下左右中央寄せ
```scss
.un_mark_wrapper {
  display: grid;
  place-content: center;
}
```
＜補足＞
- 複数要素時は `place-items: center;` を追加

---

## 解説5-親要素を子要素のサイズに合わせて左右中央寄せ
`fit-content`、`margin-inline`
```scss
.bl_info_img {
  width: fit-content;
  margin-inline: auto;
}
```
＜補足＞
- `fit-content` は「要素のサイズを要素内のコンテンツのサイズに依存して決める」という値
- `margin: 0 auto;` だと不用意に上下をリセットしてしまっているのでよろしくない
- `display: inline-block` でも左右中央寄せは実現できるが、`fit-content` による実装は「ブロック要素だったらブロック要素」、`inline-block` による実装は「インライン要素」になるという違いがある
- `margin-inline`は「論理プロパティ」（言語の書字方向に依存しない）と呼ばれるもので `margin` のような「物理プロパティ」とは異なるということは理解しておきたい（横書きのテキストの流れの向きや、縦書きなど「方向が変わると見た目が変わる」）

---

## 解説6-上下限つきのレスポンシブ文字サイズ
`font-size: clamp(最小値、推奨値、最大値)`
```scss
.bl_header_title {
  font-size: clamp(1rem, 4vw, 3rem);
}
```
＜補足＞
- もちろん `clamp` は `font-size` だけではなくサイズ系のプロパティ全般（`width`, `height`, `padding`, `margin`, `gap`など）で使える

---

## 解説7-要素の切り抜き
`clip-path`
```scss
.un_mark {
  width: 100px;
  height: calc(100px * 0.866); // 0.866 ≒ ルート3 / 2
  background-color: var(--color1);
  clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
```
＜補足＞
```scss
clip-path: inset(); // 長方形（中からの余白）
clip-path: circle(); // 円
clip-path: ellipse(); // 楕円
clip-path: polygon(); // 多角形
clip-path: path(); // SVGのパス定義
clip-path: rect(); // 長方形（座標ベース。MDNには載っているが非推奨？）
clip-path: xywh(); // 長方形（SVG風の位置とサイズ）
```
- 便利なツール　https://10015.io/tools/css-clip-path-generator

---

## 解説8-親行列の行や列に子行列を整列
`subgrid`
```scss
.bl_card_list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: 1rem;
  width: 100%;
}

.bl_card_item {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4; // 親要素の何行分の高さか
  row-gap: 0.2em; // 要素のグリッド行の間のすき間
  width: 100%;
  padding: 1em;
}
```
＜解説＞
`repeat(auto-fit, ...)`
自動で列数を調整して繰り返す

`minmax(min(300px, 100%), 1fr)`
各カラムの最小サイズと最大サイズを設定。`min` 関数を使うことで親要素が指定横幅を下回った場合でも横スクロールバーを表示させないようにしている

---

## 解説9-ピルボタン
`border-radius: 100vmax;`
```scss
.bl_card_button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 3em;
  margin-top: 1em;
  color: var(--color-white);
  background-color: var(--color1);
  border: solid 3px currentcolor;
  border-radius: 100vmax;
}
```
＜補足＞
- `border-radius` にはボタンの高さの半分の数値を入れれば実現できるのだが、高さが変わる可能性があるときや、スタイルを汎用的に使い回したいときには、大きな数字を設定しておくと便利
- とはいえ `border-radius`に 9999px などの膨大な数値を設定する方法はスマートではない
- `vmax`は `vh` と `vw`、大きい方の値が入る。100vw = 1200px、100vh = 800px の場合、100vmax は 1200px。ピルボタンはブラウザ幅内には収まっているだろうというアイデア
- TailWind の [rounded-full](https://tailwindcss.com/docs/border-radius) クラスでは `calc(infinity * 1px);` が採用されている

---

## 解説10-ホバー
`any-hover`、`currentcolor`
```scss
.bl_card_button {
  color: var(--color-white);
  border: solid 3px currentcolor;
}

@media (any-hover: hover) {
  .bl_card_button:hover {
    color: var(--color-red);
  }
}
```
＜解説＞
- タッチデバイスによってはホバー状態が残ったり、ダブルタップでないと動作しなくなることがあるため、タッチデバイスはホバー設定を除外すべき
- `any-hover` は「入力デバイスのいずれかに `hover` に対応している入力デバイスが含まれる場合に適用する」。`@media (hover: hover) and (pointer: fine)` よりもカバー範囲が広い（`@media (hover: hover)...` だと iPad の Magic Keyboard のカーソル操作で hover が適用されないらしい）
- 要素の `color` プロパティを参照する `currentcolor` を使えば変更箇所が少なくなり意図も反映されるので更新負荷の軽減が期待できる

---

## 解説11-親要素サイズでの切り替え
`@container`
```scss
@container (width > 380px) {
  .bl_card_title span {
    font-size: 1.5rem;
    color: var(--color-red);
  }
}
```
＜解説＞
- `@media`による「画面サイズでの切り替え」ではなく「親要素サイズでの切り替え」は、「コンポーネント単位でのスタイル変更」「複数の条件による自由な設定」が可能
- 親要素に`container-type: inline-size;` の設定が必要だが、今回は `.bl_card_item` に設定すると、`subugrid` と相性が悪いのかうまく動作しなかったので、 `@container` 用のラッパー（.bl_card_titile）を用意してそちらに指定することで対応した。

---

## まとめ

参考記事を読むだけでなく実際に手を動かして実装してみることで、よりモダンな CSS に慣れ親しむことができました。
今後の制作に積極的に活かしていきたいと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221915/5eca9f4c-4ad9-43c3-a800-d1baaf4899e0.png" width="300">

---

## 参考サイト
https://zenn.dev/necscat/articles/bc9bba54babaf5

https://www.tak-dcxi.com/article/that-css-technique-you-learned-is-outdated/

---