49
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

モダンCSSへのアップデート(実践編)

Last updated at Posted at 2025-04-23

目次


はじめに

そもそもなぜモダンCSSへのアップデートが必要なのだろうか?

  • 学習コストに恩恵が見合わない気がする
  • わざわざ Can I use で対応状況を確認する手間をかけてまで使う必要ある?
  • 使い慣れた記述のほうが悩まないで済むし安心
  • 忙しくて余裕がないから今はまだいい。そのうち、そのうちにね…

アップデートによるメリット

調べたり考えたりしたことをまとめてみました。

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

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


サンプルページ

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

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

@layer

「カスケードレイヤー」 という機能を使うことで、スタイルの 適用優先順位 をレイヤー(階層)として明示的 に管理できる

@layer reset, base, layout, block, unique, utilities;

@layer reset {
...
}

@layer base {
...
}

...

<解説>

  • 今回、バンドラーは簡易的に Parcel を採用したが、Parcel だと SCSS ファイルの先頭に @use を書かないと怒られるので @layer を下に持ってきた
@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(最弱)

: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回に分割されているのは粒度の違いを強調し可読性を高めるため

<補足>

// text-wrap は要素の中のテキストを折り返す方法を制御するプロパティ
text-wrap: wrap; // テキストは適切な文字で行をまたがって折り返す。既定値
text-wrap: nowrap; // テキストは行をまたがって折り返されない
text-wrap: balance; // テキストは各行の文字数を均等にする方法で折り返す
text-wrap: pretty; // wrap と同様だがパフォーマンスよりも精度優先
  • Webサイトではよくタイトルが横幅より短めにデザインされるので balance は有用であるが、コンピューターに負荷がかかるため限られた行数(Chromium では 6 行以下、Firefox では 10 行以下)のテキストブロックにのみ対応となっている点に注意
all: unset; // その要素のすべてのプロパティを、既定値が inherit のものは継承値に、そうでなければ初期値に変更する
display: revert; // revert は「ある要素に適用されたスタイルを、その要素が本来持っていたであろうスタイルに戻す」というもの。ここではたとえば「div は inline ではなく block 」というようにデフォルトのdisplay 状態に戻している

解説3-親要素いっぱいまで広げる

inset

.un_mv_inner {
  position: absolute;
  inset: 0;
}

<解説>

  • insettop left right bottom を一括指定できるショートハンド
  • inset: 10px; のように指定すると、親要素に 10px の padding があるような見た目になる

解説4-上下左右中央寄せ

.un_mark_wrapper {
  display: grid;
  place-content: center;
}

<補足>

  • 複数要素時は place-items: center; を追加

解説5-親要素を子要素のサイズに合わせて左右中央寄せ

fit-contentmargin-inline

.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(最小値、推奨値、最大値)

.bl_header_title {
  font-size: clamp(1rem, 4vw, 3rem);
}

<補足>

  • もちろん clampfont-size だけではなくサイズ系のプロパティ全般(width, height, padding, margin, gapなど)で使える

解説7-要素の切り抜き

clip-path

.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%);
}

<補足>

clip-path: inset(); // 長方形(中からの余白)
clip-path: circle(); // 円
clip-path: ellipse(); // 楕円
clip-path: polygon(); // 多角形
clip-path: path(); // SVGのパス定義
clip-path: rect(); // 長方形(座標ベース。MDNには載っているが非推奨?)
clip-path: xywh(); // 長方形(SVG風の位置とサイズ)

解説8-親行列の行や列に子行列を整列

subgrid

.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;

.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 などの膨大な数値を設定する方法はスマートではない
  • calc(infinity * 1px); という指定方法もあるが、記述量も多いし値も過剰
  • vmaxvhvw、大きい方の値が入る。100vw = 1200px、100vh = 800px の場合、100vmax は 1200px。ピルボタンはブラウザ幅内には収まっているだろうというアイデア

解説10-ホバー

any-hovercurrentcolor

.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

@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 に慣れ親しむことができました。
今後の制作に積極的に活かしていきたいと思います。


参考サイト


49
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?