🛍️

Learn CSS学習メモ

2022/04/03に公開

Learn CSS を眺めているので学習メモ。

規格

関連する規格がAll CSS Specificationsにまとまっているので、適宜参照する。CSSはCSS2を部分的に置き換える形で定義されているが、そのパッチの一覧をまとめたCSS Snapshot 2021等の文書がある。

ボックスとレイアウトについて

001 Box Model, 008 Layout

Box modelに関するMDNの入門記事もよくまとまっている。

Learn CSS 001 の概略

  • 要素の占める領域はmargin box, border box, padding box, content boxの4つの領域によって代表され、原則としてこの順に小さくなる。
    • ただし、boxはその親・祖先にあたるboxからはみ出ることもある。その場合の表示がどうなるかは overflow プロパティなどの指定によって変化する。
  • boxの大きさは親要素側の制約から決定する方法 (extrinsic sizing) と子要素側の制約から決定する方法 (intrinsic sizing) がある。

boxの2つの用法

CSSにおいて "box" は2つの意味で使われているので注意が必要。

  • 論理的なbox ... CSS-DISPLAYで定義されているboxとは、レイアウト計算前に論理的な単位として作られるノードで、およそ1要素につき1つ存在する。論理的なboxは互いに論理的な包含関係を持ち、木構造 (box tree) を形成する。
  • 物理的なbox ... 1つの論理的なboxに対して、4つの領域が算出される。これらはmargin box, border box, padding box, content boxと呼ばれる。物理的なboxはそれぞれ、1つ以上の矩形の合併である。
  • area ... 次の領域を指す。
    • margin area = margin box - border box
    • border area = border box - padding box
    • padding area = padding box - content box
    • content area = content box

各領域の機能

要素の占める領域はmargin box, border box, padding box, content boxの4つの領域によって代表され、原則としてこの順に小さくなる。それぞれの用途は?

  • Margin box
    • 隣接要素・親要素との位置関係の決定 (ただしマージンの相殺に注意が必要)
      • top, left, bottom, rightによる座標指定も含む
  • Border box
    • サイズ指定の基準 (box-sizing: border-box; の場合)
    • 背景 (デフォルト: background-clip: border-box; の場合)
    • マウスのホバー判定 (Chrome, Firefoxの場合[1])
  • Padding box
  • Content box
    • サイズ指定の基準 (デフォルト: box-sizing: content-box; の場合)
    • 背景 (background-clip: content-box; の場合)
    • 内部の要素との位置関係の決定
      • top, left, bottom, rightによる座標指定も含む
  • 他にも色々ありそう (要調査)

インライン要素とボックス

要素の占める領域はmargin box, border box, padding box, content boxの4つの領域によって代表され、原則としてこの順に小さくなる。ではインライン要素の場合のボックスの扱いはどうだろうか?

結論から言うとboxは名前の通りに単独の矩形であるとは限らず、複数の矩形の合併の場合がある。個々の矩形は断片 (fragment) と呼ばれる

boxが複数の断片に分かれるとき、周縁部の構造 (outline, box-shadow, margin, border, padding など) はデフォルトで不完全な状態で描画される。

画像: インラインボックスのpadding, border, margin

box-decoration-breakを変更すると、各断片で閉じた周縁部を生成することもできる。

断片は以下のような状況で生成される。

  • インライン要素が途中で折り返されたとき。 (インライン軸、つまり横方向に分割される)
  • columnsが設定された要素の中で次のカラムへの折り返しが発生したとき。 (ブロック軸、つまり縦方向に分割される)
  • 印刷用のレンダリング等で文書が複数のページに分割され、要素がページをまたいで分割されたとき。

インライン要素のボックスモデルについてはUnderstanding the CSS box model for inline elements – Mozilla Hacks - the Web developer blogがわかりやすい。↑の話に加えて以下のような話も説明されている。

  • 縦方向のレイアウトはcontent boxに基づいて行われる。

制約の優先度

boxの大きさは単方向フローで自然に決定されるわけではなく、様々な向き・強さの制約を書ける。結果として、ほとんどの場面で制約集合は過剰 (over-constrained) か過少 (under-constrained) になる。では、制約集合が過剰・過少だった場合にはどのような優先度で決定されるのか?

flexbox, grid, min-content/max-content/fit-content 等が登場する以前の基本的な制約解決はCSS2§10で説明されているのでまずこれを見る。

width, min-width, max-widthの関係

min-width, max-width, min-height, max-heightには独立した小節が設けられて、そこで説明されている。ざっくり言うとこれらは制約違反時にwidth/heightの計算をやり直す仕組みとして定義されていて、幅の計算は最大3回行われることになる。

(なぜそうする必要があるかというと、他の制約との関係で、幅に絶対値を指定していたとしてもその値をそのまま採用できるとは限らないからである。)

  1. width プロパティのcomputed valueを使って幅を計算する。 (width の仮のused value (A))
  2. (A) が max-width のcomputed valueよりも大きかった場合は、 max-width を使って幅を再計算する。 (width の仮のused value (B))
    • そうでなかった場合は (A) の値を (B) として次のステップに進む。
  3. (B) が min-width のcomputed valueよりも小さかった場合は、 min-width を使って幅を再計算する。 (width のused value (C))
    • そうでなかった場合は (B) の値を (C) とする。

つまり、過剰制約の場合は min-width > max-width > width の順で優先される。heightも同様。

https://codepen.io/qnighy/pen/oNpyZKo

width/heightとmarginの関係

extrinsic sizingモードではcontaining blockの大きさからboxのサイズが決定される。boxの大きさを構成する4種の寸法 (width/height, padding, border width, margin) のうち、paddingとborder widthは絶対値またはcontaining blockからの相対値として決定されるが、width/heightとmarginはいずれもauto値をとることができるため、過剰/過少制約の源になっている。これはどのように解決されるか?

flow中のブロック要素の場合

古典的なレイアウトモードの場合はCSS2§10で説明されている。そのうち最もよく出てくるのがflow中のブロック要素の場合で、§10.3.3にあるのでまずこれを説明する。

widthとheightで異なるので、まずwidth (インライン軸の大きさ) を確認する。基本的に解きたいのは以下の方程式。 (右辺はこの時点で計算済み)

  • (margin-left) + (width) + (margin-right) = (width of containing block) - (border-left-width) - (border-right-width) - (padding-left) - (padding-right)

古典的なフローレイアウトではブロック要素は全幅を占有することが想定されているので、できるだけwidthを大きくとるように解決される。width, marginがそれぞれautoかどうかで場合分けして規定を解釈すると以下のようになる。

width margin-left,
margin-right
指定あり 両方指定あり margin-rightの制約を無視して左詰めにする
(ltrの場合)
指定あり 片方auto 方程式を解いてauto marginを決定する
結果が負になった場合は0に置き換え、表の一番上の条件で計算し直す
指定あり 両方auto margin-left = margin-right として連立方程式を解く (→中央揃え)
結果が負になった場合は0に置き換え、表の一番上の条件で計算し直す
auto 両方指定あり 方程式を解いてwidthを決定する
結果が負になった場合は0に置き換え、表の一番上の条件で計算し直す
auto 片方auto auto marginは0として連立方程式を解く
結果が負になった場合は0に置き換え、表の一番上の条件で計算し直す
auto 両方auto auto marginは0として連立方程式を解く
結果が負になった場合は0に置き換え、表の一番上の条件で計算し直す

ただし、CSS2でオーバーフロー時の挙動を定めた以下の記述

If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.

は不十分で、width: autoを計算して負になるケースが考慮されていない。そこで上の表では実験事実に準じて、width: autoの場合もwidth: 0として上記のルールを当てはめるものと解釈した。

表の記述を言い換えると、以下の順番でルールが優先されている。

  1. widthの指定
  2. widthは0以上
    • widthの指定は0以上なので、1と2の順序関係はない
  3. margin-leftの指定
  4. オーバーフロー時は左揃え (ltrの場合)
  5. margin-rightの指定 (ltrの場合の順番)
  6. auto marginは0以上
  7. 中央揃え

https://codepen.io/qnighy/pen/MWrXoeQ

続いてheight (ブロック軸の大きさ) を確認する。

古典的なフローレイアウトではページは縦に伸ばせることが想定されているので、heightはなるべくintrinsicに (要素内部から来る情報に依存して) 決定される。また、CSS2§10には明示的には書かれていないが、heightはwidthのused valueに依存して決定される

https://codepen.io/qnighy/pen/NWXzwpj

細かいルールはCSS2§10.6.3, CSS2§10.6.6で説明されているが、主なポイントは以下。

  • margin-top, margin-bottomはautoの場合常に0になる。
  • heightは内部のフローレイアウトの結果から算出される (そのため、widthが必要)。

そのため、横方向とは異なり、縦方向に関してはmarginによる中央揃えはできない (フローレイアウトの場合)。

floatなブロック要素の場合

auto marginは0として扱われる。

このケースはCSS2§10.3.5CSS2§10.6.6で扱われているが、実は以下の関係がある。

  • float時の大きさのused value = min(max(min-content, available size), max-content)

このことはCSS-SIZING-3§5.1で説明されている。min-content, max-contentはCSS-SIZING-3§2.1でも手短に説明されているのでこちらを読むのがわかりやすい。

このように計算される幅はshrink-to-fitという名前がついている。float以外の場合からも使われるので覚えておくとよい。

positionが指定されている場合

座標指定の場合はフローレイアウトの場合に近いが、left/right/top/bottomがmarginと同様にauto値を取れるので、より複雑になっている。

widthの計算 (CSS2§10.3.7) はフローレイアウト時のwidth計算をおおよそ以下のように置き換えたもの。

  • widthに自由度があるときは、できるだけ大きくとるのではなくshrink-to-fitを用いる。
  • left, rightの少なくとも片方がautoのときは、marginの自由度は使わない (autoは0とみなす)。
  • left, rightの自由度を用いて左寄せ (ltrの場合) をするときは、0にするのではなくstatic position (=もし実際とは異なり当該要素がフローレイアウトの対象だった場合に配置されるであろう位置) を基準に配置する。

heightの計算 (CSS2§10.6.4) は上記のwidthの計算とほぼ同じ。 (フローレイアウトのheightの計算とは異なる点に注意) ただし以下のような差異がある:

  • heightに自由度があるときは、できるだけ大きくとるのではなくwidthの計算結果に基づいてレイアウトしたときのintrinsic heightを用いる。
  • top, bottomの自由度があるときは上寄せ (ブロック軸のスタート側方向への寄せ) となる。

座標指定の場合は高さ方向でもextrinsicな制約を利用するため、たとえばmargin: auto; による縦方向の中央揃えも可能になる。これはフローレイアウトの場合と大きく異なる。

flexboxの場合

gridの場合

width/heightとアスペクト比の解決順

書記方向が内側と外側で異なる場合

intrinsic sizingとextrinsic sizingの解決順

以下続く (多分)

脚注
  1. マウスのホバー判定については実験的に確認した。判定対象の領域に関しては、おそらく現時点では仕様化されていない。たとえばCSS-UI-3内, W3C Wiki: Hit Testing, :hover の定義, mouseoverの定義 などを参照。 ↩︎

Discussion