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

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

くっつかない理由を祖先に探す

TT Labで続きを見る

目標

包含ブロック・スクロールコンテナー・スタッキングコンテキストを、祖先をたどって上がりながら自分で計算します。ブラウザーはありませんが、この3つはマークアップとCSSだけで答えが決まります。

なぜ重要なのか

fixedが固定されずstickyがついてこないバグは、症状が原因をまったく指し示しません。その要素のCSSは完璧に正しく、開発者ツールでその要素だけを見ても、何も問題がありません。見るべき場所は祖先です。

祖先にtransformが1つあると、fixedの基準がビューポートからその祖先に変わります。祖先にoverflow: hiddenが1つあると、そのボックスがスクロールコンテナーになり、stickyが固定される場面がなくなります。カードのホバー効果とモーダルは無関係に見えるため、ルールを知らないと、原因を見つけるまでに何時間もかかります。

このラボの文書

採点ツールと計算ツールが、次の構造を想定します。HTMLは書きません。

div.page
├ header.topbar
└ div.shell
  ├ aside.side
  │ └ nav.toc
  └ main.content
    └ div.card
      ├ span.badge
      └ div.overlay

作るもの

/root/work/pos/layout.cssの1つだけです。

計算ツール

cd /root/work/pos
python3 /opt/lab/checks/css-pos-lab/boxes.py layout.css
python3 /opt/lab/checks/css-pos-lab/boxes.py layout.css 500

要素ごとに、position・包含ブロック・stickyの判定・z-indexの範囲を出力します。2つ目の引数として画面幅を渡すと、その幅で再計算します。

ステップ

  1. .badgeを.card基準に(絶対位置の包含ブロック)
  2. .overlayをビューポート基準に(fixedの標準の包含ブロック)
  3. transformをどこに置くか: fixedが閉じ込められる落とし穴
  4. .topbarをstickyに(しきい値)
  5. .tocが実際に固定されるように(スクロールコンテナーを選ぶ)
  6. z-indexがルートで競うように(スタッキングコンテキスト)
  7. 計算ツールの結果を07-boxes.txtに
  8. 狭い画面では目次をフローに(順序の落とし穴をもう一度)
  9. まとめ → 09-notes.md

参考

絶対位置の基準を作る

/root/work/pos/layout.cssを作成し、.badgeをposition: absoluteにして、方向を2つ以上(例: topとright)指定してください。その包含ブロックが.cardになる必要があります。

mkdir -p /root/work/posを実行します。absoluteの包含ブロックは、最も近い位置指定された祖先です。relativeは、自分の位置をそのままにしつつ基準だけを作ってくれるため、この用途で最もよく使われます。

オーバーレイをビューポートに固定する

.overlayをposition: fixedにして、4つの方向をすべて決めてください(inset: 0の1行でも構いません)。z-indexも指定してください。包含ブロックがビューポートである必要があります。

fixedの標準の包含ブロックはビューポートです。そのため、スクロールしてもついてこず、画面に固定されています。4つの方向がすべて決まって初めて、画面全体を覆います。

transformをどこに置くか

.badgeにtransformを指定して、角の外へ少しはみ出させてください。.cardには、transform・filter・perspective・will-change・containのどれもあってはいけません。そして、.overlayの包含ブロックは、引き続きビューポートである必要があります。

まず.cardにtransform: translateZ(0)を入れて、計算ツールを実行してみてください。.overlayの包含ブロックが.cardに変わります。それが、このラボが見せようとしている落とし穴です。確認できたら、その属性を、実際に動かす必要のある要素に移してください。

ヘッダーバーを固定する

.topbarをposition: stickyにして、topを指定してください。計算ツールが「固定される」と判定する必要があります。

しきい値(top・right・bottom・leftのうち1つ以上)がないと、固定される線がないので、そのまま流れていきます。エラーも警告も出ないという点が、このバグを長く生き残らせます。

スクロールコンテナーを選ぶ

.tocをposition: sticky; top: 64pxにし、.pageにoverflow-y: autoを指定して、.pageがスクロールコンテナーになるようにしてください。.shellと.sideには、visibleではないoverflowがあってはいけません。

stickyは、最も近いスクロールコンテナーの中でだけ固定されます。途中のコンテナーにoverflow: hiddenが1つあると、それがスクロールコンテナーになり、ユーザーはそのボックスをスクロールしないため、固定される場面が生まれません。overflow-x: hiddenも、同じ結果になります。

z-indexが通用する範囲

.topbarにz-indexを指定し、.overlayのz-indexをそれより大きくしてください。.page・.shell・.content・.cardのどれも、スタッキングコンテキストを作ってはいけません。

祖先がスタッキングコンテキストを作ると、その中のz-indexは、どれだけ大きくても外へ出られません。opacity: 0.99やtransformのような何気ない1行が、それを作ります。計算ツールが、「z-indexがどのボックスの中で通用するか」も一緒に出力します。

計算ツールで確認する

boxes.pyを実行して、結果を/root/work/pos/07-boxes.txtに残してください。出力に「固定されない」がなく、要素の行が切れずにすべて入っている必要があります。

python3 /opt/lab/checks/css-pos-lab/boxes.py layout.css | tee 07-boxes.txtを実行します。包含ブロックの横のかっこが、なぜそう決まったのかを教えてくれます。祖先の名前と、その属性です。

狭い画面ではフローにする

@media (max-width: 700px)で、.tocをposition: staticに戻してください。広い画面(1280px)では、引き続き固定される必要があります。メディアクエリは、基本ルールの下に置く必要があります。

詳細度が同じなら、あとに書いたものが勝ちます。メディアクエリの中にあるからといって、より強いわけではありません。基本ルールをメディアクエリの下に置くと、メディアクエリは何もできなくなります。boxes.py layout.css 500で、狭い画面を確認してください。

診断の順番を書き留める

/root/work/pos/09-notes.mdに3行以上(120文字以上)書いてください。本文に담음 블록、transform、overflowが含まれている必要があります(韓国語で「包含ブロック」を意味する語を含みます)。シートも、引き続き前の段階の基準を通過する必要があります。

核心は1つです。原因は常に祖先にあります。次に同じ症状に出会ったとき、祖先をたどって上がりながら何を探すのかを、自分の言葉で書き留めておいてください。