TT Lab
はじめる
学ぶ 学習パス コース

CSS — 後に書いた規則が勝つ

勝つ規則を計算する

TT Labで続きを見る

目標

CSSが予測不可能に見えるのは、ルールがないからではなく、ルールが3段階だからです。重要度 → 詳細度 → 順序です。

ブラウザーがないため、「見た目が合っているか」は採点しません。代わりに、どのルールが勝つかを実際に計算します。それは、ブラウザーなしでも答えが1つに決まります。

作るもの

/root/work/css/styles.cssの1つだけです。HTMLは必要ありません。採点ツールが、次の要素を想定してカスケードを解決します。

div#app > div.card
main.lessonLayout > aside.lessonSide
main.lessonLayout.sideHidden > aside.lessonSide
div.toolbar

計算ツール

cd /root/work/css
python3 /opt/lab/checks/css-lab/cascade.py styles.css

ルールごとに、順序・メディア条件・詳細度を表示します。扱う範囲は、タグ・クラス・ID・擬似クラス・子孫/子結合子・カンマグループ、そして@media (max-width|min-width: Npx)です。それ以外のセレクターに出会うと、黙って読み飛ばさず、「解釈できません」と言います。

ステップ

  1. :rootにトークン3つ・var()を3か所
  2. 詳細度の予測 → 02-spec.txt
  3. !importantなしで勝つ
  4. 順序の落とし穴(基本ルールをメディアクエリの上へ)
  5. 3列のグリッド
  6. 折りたたんだら1列
  7. フレックスのツールバー
  8. 計算ツール → 08-cascade.txt
  9. まとめ → 09-notes.md

参考

ステップ4とステップ6は、このサービスで実際に起きたバグです。モバイルの目次オーバーレイが開かなかったことと、目次を折りたたむとページが33,000pxに伸びたことです。

トークンを:rootに置く

/root/work/css/styles.cssを作成し、:rootにカスタムプロパティを3つ以上定義したうえで、var()を3か所以上で使ってください。

mkdir -p /root/work/cssを実行します。名前は--accent、--gap、--fgのように自由です。SCSSの変数と違い、カスタムプロパティはランタイムに生きていて継承されます。そのため、下位のツリーでだけ上書きできます。

詳細度を計算してみる

次の5つのセレクターの詳細度を、02-spec.txtに번호=a,b,cの形式(韓国語で「番号」を意味する語を含みます)で、1行ずつ書いてください。

1) a
2) .btn.primary
3) #nav a:hover
4) ul > li::marker
5) :where(.card) .title

aはID、bはクラス・属性・擬似クラス、cはタグ・擬似要素です。例: 1=0,0,1。5つ目のセレクターが落とし穴です。:where()は常に0として数えます。位の間では繰り上がらないということも覚えておいてください。クラス11個でも、ID1つには勝てません。

!importantなしで勝つ

.card { color: ... }を先に書き、#appの中の.cardだけが別の色になるようにしてください。!importantを使ってはいけません。

採点ツールが、div#app > div.cardの要素に対してどのルールが勝つかを、実際に計算します。#app .cardは1,1,0なので、.card(0,1,0)に勝ちます。!importantは別の!importantでしか勝てないため、一度使うと、そのプロパティを永遠にそのように扱わなければなりません。

順序の落とし穴を避ける

.lessonSideのpositionを、基本はsticky、max-width: 900pxのときはfixedになるようにしてください。2つのルールの詳細度は同じなので、順序が答えを決めます。

基本ルールを@mediaブロックの上に置いてください。下に置くと、詳細度が同じなので(どちらも0,1,0)、あとに書いた基本ルールがモバイルでも勝ちます。採点ツールが、1280pxと500pxの2つの幅で、それぞれ計算します。このサービスでモバイルのオーバーレイが開かなかったバグが、まさにこれでした。

骨格をグリッドにする

.lessonLayoutをdisplay: gridにして、3列のgrid-template-columnsとgapを指定してください。

例: grid-template-columns: 240px 1fr 200px。グリッドは、行と列の両方を決めたいときに使います。1行の中で分けるだけなら、フレックスが適しています。

折りたたんだら列もなくす

.lessonLayout.sideHiddenのとき、列が1つになるようにしてください。0 1fr 200pxのように、幅0の列を残してはいけません。

子がdisplay: noneだと、グリッドから完全に外れます。列を0にしておくと、本文がその0pxの列に押し込まれて文字ごとに改行され、実際にページが33,000pxになりました。列の数を減らしてください。grid-template-columns: 1frです。

ツールバーはフレックスにする

.toolbarをdisplay: flexにして、justify-content、align-items、gapをすべて指定してください。

最もよく使う組み合わせは、space-betweenとcenterとgapの3つです。1つだけを右へ寄せたいときは、その要素にmargin-left: autoを指定する方法もあります。

計算ツールで確認する

/opt/lab/checks/css-lab/cascade.pyで自分のスタイルシートを解決し、結果を08-cascade.txtに残してください。

python3 /opt/lab/checks/css-lab/cascade.py styles.css | tee 08-cascade.txtを実行します。ルールごとに、順序・メディア条件・詳細度が出力されます。開発者ツールで取り消し線が引かれたルールを見るとき、頭の中でしている計算が、これです。

なぜ負けたのかを説明する

09-notes.mdに3行以上書いてください。ステップ4で順序を逆にすると何が起きるか、ステップ6で幅0の列を残すと何が起きるか、そして!importantを使いたくなったときに最初に見るものを書きます。

本文に특정성、순서、그리드が含まれている必要があります(韓国語で「詳細度」「順序」「グリッド」を意味する語を含みます)。診断は、いつも4つの段階です。セレクターが合っているか、負けているか、詳細度か順序か、継承を期待しているか。