勝つ規則を計算する
目標
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)です。それ以外のセレクターに出会うと、黙って読み飛ばさず、「解釈できません」と言います。
ステップ
:rootにトークン3つ・var()を3か所- 詳細度の予測 →
02-spec.txt !importantなしで勝つ- 順序の落とし穴(基本ルールをメディアクエリの上へ)
- 3列のグリッド
- 折りたたんだら1列
- フレックスのツールバー
- 計算ツール →
08-cascade.txt - まとめ →
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つの段階です。セレクターが合っているか、負けているか、詳細度か順序か、継承を期待しているか。