CSS大解剖 16日目: 「ボックス」
本稿は、2024年3月頃に書き溜めていたシリーズです。最後まで温存させるのが勿体ないので、未完成ですがそのまま公開します(公開日: 2025/9/27)。そのため、内容の重複や記述方針の不一致があるかもしれませんが、ご理解ください。
CSSの仕様を理解するために、1日ごとにテーマを決めて説明する企画16日目です。今日のテーマは「ボックスモデル」です。
ボックスモデル
CSSのボックスモデルでは、レイアウトの対象となる矩形は以下のような階層構造となって生成されます。
- 要素 (element) は全ての起点です。文書は要素のツリー構造を与えます。
- ボックス (box) はボックス木と呼ばれる中間的なデータ構造の構成要素です。ボックス木は文書木をもとにして生成される木です。原則として、ボックス木は文書木の構造をそのまま反映しますが、各々の要素のレイアウト上の都合にあわせてボックスが追加されたり削除されたりすることがあります。
- ボックス断片 (box fragment) は、ボックスをさらに分割したものです。ボックスを領域に効率的にフィットさせるには、しばしばその内容を縦か横に分割して別々に配置したほうが有利です。この分割処理を断片化といいます。断片化が起きなければ、ボックス断片はボックスに対して1つずつ生成されます。
- ボックス断片は画面上のある連続した[1]領域を占有しますが、この領域はさらに大きさの異なる4つの異なる矩形の組み合わせで表されます。
- マージンボックス (margin box) は最も大きな矩形[2]で、マージンによって作られます。マージンボックスは主に、隣接するコンテンツや親との位置関係を決定するために使われます。
- ボーダーボックス (border box) はマージンボックスの内側にある矩形で、ボーダーによって作られます。視覚的にこのボックスが占有する範囲におおむね対応します。
- パディングボックス (paddinb box) はボーダーボックスの内側にあり、パディングによって作られます。
- コンテントボックス (content box) はパディングボックスの内側にあります。コンテントボックスは名前の通り、このボックスの子孫を格納するための基準となる領域です。パディングは、これらの子孫との距離をとるために使われます。
- そのほか、各レイアウトに固有の矩形が定義されていることがあります。たとえばインラインレイアウトでは行ボックス (line box), フレックスボックスではフレックス行 (flex line)が定義されています。これらはボックスやボックス断片を含む中間的な構造です。
ボックス
HTMLの要素の中には、単独で複数のパーツからなるものがあります。たとえば以下の例では <ul>
, <li>
, <table>
, <caption>
の4種類の要素に枠をつけて描画しています。
<!doctype html>
<style>
body {
max-width: 300px;
font-size: 2em;
}
ul {
border: 1px solid red;
}
li {
border: 1px solid blue;
}
table {
border: 1px solid red;
}
caption {
border: 1px solid blue;
}
</style>
<ul>
<li>foo</li>
<li>bar</li>
</ul>
<table>
<caption>Table 1</caption>
<tr><th>Foo</th><th>Bar</th></tr>
<tr><td>foo</td><td>bar</td></tr>
</table>
このスクリーンショットを見ると、 <li>
と <table>
は以下のように2つのレイアウト上の役割を同時に担っていることがわかります。
-
<li>
- リストアイテムのマーカー部分
- リストアイテムの本文 ← こっちにborderがついてる
-
<table>
- キャプションを含む全体
- キャプションを除き、セルを全て含む部分 ← こっちにborderがついてる
このように、文書の意味的な構造と視覚的な構造は必ずしも1対1対応しません。この不対応に対処するため、CSSでは要素のほかにボックス (box)という概念を用意しています。
ボックスは3種類に分けられます。
- 要素生成ボックス (element-generated box)
- 主ボックス (principal box)
- 主ボックス以外
- 匿名ボックス (anonymous box)
置換要素と非置換要素
<img>
が生成する画像はCSSから見ると単なる矩形領域であり、内部のレイアウトに関与することはありません、このようにCSSから見て非透過的に扱われるコンテンツを置換要素 (replaced element)と呼びます。HTMLでは§15.4 Replaced Elementsにその規定があります。それ以外の要素は非置換要素 (non-replaced element) と呼ばれます。なお、ある要素が置換要素であるかどうかのルールはそれほど単純ではなく、 <img>
であっても非置換要素として扱われることがあります。
置換要素が生成する主ボックスも便宜的に置換要素と呼ばれます。また、主ボックスではないボックスが「置換要素」と同等の状態になる場合[3]もあり、この場合も便宜的に置換要素と呼ばれます。
置換要素は内部に他のボックスを持たず、かわりに自然な次元 (natural dimensions) を持つことがあります (持たない場合もあります)。置換要素はレイアウト上の多くの場面で特別な扱いを受けます。
テキスト列
テキスト列 (text sequence)は名前の通り、テキストノードの連続した連なりに対応する概念です。テキストノードが要素ではないのと同様、テキスト列もボックスではありません。
テキストノードとテキスト列はほぼ1対1で対応しますが、以下の例外があります。
- 正規化されていないDOMツリーにおいて、空のテキストノードはテキスト列を生成しない。
- 正規化されていないDOMツリーにおいて、連続するテキストノードは連結される。
空白の扱い
また、空白文字のみからなるテキスト列も有効なテキスト列であることには注意が必要です。手書きのHTMLでは、このようなテキスト列が頻繁に出現します。たとえば以下の例を考えます。
<div>
<p>Hello!</p>
</div>
この例では、 <div>
から <p>
までの間と </p>
から </div>
までの間にテキスト列が存在します。
レイアウトの中には、インラインコンテンツが途中で出現すると困るものがあります。このような場合では、空白のみからなるテキスト列は無視し、それ以外のテキスト列は匿名のボックスで包含するように定義されるのが一般的です。また、空白文字は white-space
プロパティの指定によっても除去されることがあります。
ボックスの生成
ボックスが生成される条件には、たとえば以下のようなものがあります。
- 主ボックス
-
display: none;
やdisplay: contents;
の場合を除き、要素に対して自動で生成されます。-
display: none;
の場合は、その要素の子孫の全てのボックス生成が停止されます。 -
display: contents;
の場合は、その要素自身に由来するボックスの生成が停止されます。そのかわり、あたかも当該要素が要素の内容で置換されたかのようにボックスを生成します (つまり、子孫要素のボックスは生成されます)。
-
-
- 主ボックス以外の要素生成ボックス
-
display
がlist-item
キーワード を含む場合。この場合、マーカーボックス (::marker
) が生成されます。生成位置はlist-style-position
の値に依存し、外側(主ボックスの直前)または内側(主ボックス内の先頭)に生成されます。 -
display: table
の場合。この場合、主ボックスである表ラッパーボックスに加えて、表グリッドボックスが生成されます。 - 複数列レイアウトが有効な場合。この場合、列ボックスが生成されます。
-
::before
/::after
擬似要素が空でないcontent
プロパティ値を持つ場合。この場合、これらの擬似要素に対応するボックスが主ボックスの内側に生成されます。 -
::first-letter
擬似要素にスタイルが当てられた場合。この場合、この擬似要素に対応するボックスが、当該の文字を囲むように生成されます。
-
- 匿名ボックス
- ブロックコンテナ内にインラインレベル要素またはテキストとブロックレベル要素が混在している場合、それらを包含するための匿名ブロックボックスが作られます。
- さらに、インラインレベル要素またはテキストのみを持っているブロックコンテナの内側には、ルートインラインボックスが新たに作られます。これはCSS2の匿名インラインボックスと同等のものです。
- Flexコンテナ内に非自明なテキストがある場合、それを包含するための匿名のFlexアイテムが生成されます。
- グリッドコンテナ内に非自明なテキストがある場合、それを包含するための匿名のグリッドアイテムが生成されます。
- テーブル用の
display:
プロパティ値を持つ要素には、所定の木構造が要求されます。これらに違反する場合には、構造を修正するためにテーブル用の匿名ボックスが生成されます。 - ルビ用の
display:
プロパティ値を持つ要素には、所定の木構造が要求されます。これらに違反する場合には、構造を修正するためにルビ用の匿名ボックスが生成されます。 - HTMLの規定により、
<fieldset>
の内側に匿名fieldset内容ボックスが生成されます。 - HTMLの規定により、ボタンレイアウトを持つ
<button>
要素および<input>
要素は、内部display型が flow-root または flow の場合 (flowの場合はflow-rootとして扱われる)、その内側に匿名ボタン内容ボックスを生成します。
レイアウト生成ボックス
前述したボックス生成規則の中には、レイアウト処理に入るまでその結果を決定できないものがいくつかあります。
-
匿名ブロックボックスは実際にはボックスではなく断片の集合を含んでいると考える必要があります。たとえば以下のコードを考えます。
<p><span>Hello, <div>ads here</div> world!</span></p>
この場合、<div> はブロックレベル要素であるため、その上下にはそれぞれ独立した匿名ブロックボックスが生成されます。上のボックスには
Hello,
が含まれ下のボックスにはworld!
が含まれますが、いずれも<span>
の一部であるため、<span>
は両方のボックスにまたがって存在することになります。これは断片化前の状態では適切なボックス木として表現することは不可能であり、断片化後にそのような木構造としての整理が行われると考えたほうが適切です。断片化はレイアウト処理と不可分であるため、レイアウト前の段階では匿名ブロックボックスは存在しないと考えたほうが自然です。
-
複数列レイアウトにおいて、列の個数はレイアウトの結果に依存することがあります。この場合、列ボックスをあらかじめ生成しておくことはできません。たとえば、列ボックスははじめ1つだけあるものと考え、レイアウトの途中で必要に応じてボックスを増やすと考えることで整理することができます。
content
プロパティによる変形
Generated Content Level 3では content
プロパティの拡張が提案されています。この提案では、content: contents
により要素の中身を ::before
または ::after
内に移すことを可能にしようとしています。
content: contents
はボックスツリーを大きく変形するため、レイアウトアルゴリズムの他の部分との関係については規定すべき部分が多くあると考えられますが、現段階ではそれらは詳しく定義されていません。これらは実装が進むとともに整備されるものと考えられます。
display: run-in
による変形
display: run-in
は追い込みレイアウト (run-in layout) を形成します。追い込みレイアウトは、コンテンツを次の段落に統合するというレイアウトです。追い込みレイアウトの適用前は、追い込みボックスとブロックレベルボックスは兄弟関係にありますが、追い込みレイアウトを適用すると追い込みボックスはブロックレベルボックスの子になるため、これはボックス木に対する変形操作であると理解できます。
追い込みレイアウトによるボックス木の変形は、フローレイアウトのレイアウト処理の直前に行われます。つまり、他の処理との前後関係は以下のようになると考えられます。
-
display: list-item
や::before
/::after
などによる要素生成ボックスの処理よりは後に行われます。 - レイアウトに由来するボックス生成 (匿名ブロックボックスなど) よりは前に行われます。
なお、追い込みレイアウトは::marker
ボックスを特別扱いし、再挿入位置決定時にこれらのボックスをスキップします。
CSS Regionsによる変形
現在策定中のCSS Regionsという仕様では、複数の要素 (名前つきフロー) からコンテンツを集約して、別の複数の要素 (リージョン鎖) に分割して流し込むという機能が提案されています。
CSS Regions はボックスツリーに大きな影響を与える強力な規格ですが、まだ実装も規格策定もそれほど進んでいないため、本稿では詳しくは扱わないことにします。
要素生成ボックス同士の関係
要素生成ボックス同士の関係についてはPseudo Elements Level 4で策定中です。この部分については未確定・未実装な点が多く見られますが、現在のところの規定は以下のようになっています。
-
::marker
自身はlist-itemとなれず、::marker::marker
は存在しません。 -
::before::before
は存在しません。同様に、::marker::before
も存在しません。 -
::before
/::after
はlist-itemとなることが可能で、::before::marker
は存在します。 -
list-style-position: inside
の場合の::marker
は同じ要素の::before
の前に生成されます。
ボックスのプロパティ
要素と同様、ボックスもプロパティを持ちます。ボックスのプロパティは以下のように決定されます。
- 主ボックスのプロパティは、原則として要素のプロパティをそのまま引き継ぎます。ただし、要素が複数のボックスを生成する場合は、明示的に一部のプロパティを引き継がないように指定が行われることがあります。 (たとえば、
display: table
な要素の表ラッパーボックスには、marginやinsetなどの一部のプロパティ以外は適用されません) - 主ボックス以外の要素生成ボックスのプロパティは、原則として主ボックスを親としたカスケード計算によって算出されます。したがって、擬似要素セレクターを用いたスタイル指定がなければ継承プロパティは主ボックスと同じ値になり、非継承プロパティは初期値になります。ただし、要素が生成される条件によっては、特別に一部のプロパティの計算方法が変更されることがあります。 (たとえば、
display: table
な要素の表グリッドボックスには、 marginやinsetなどの一部のプロパティを除いた全てのプロパティが要素のプロパティから設定されます)- たとえば
display: list-item
な要素のマーカーボックス::marker
はデフォルトで主ボックスの外に生成されるため、この場合は主ボックスとマーカーボックスには親子関係はありません。それにもかかわらず、color
などの継承プロパティを主ボックスから継承します。
- たとえば
- 匿名ボックスのプロパティは、原則としてボックス木の構造に従って初期値または継承値が使われます。ただし、それぞれの生成条件ごとに指定されたスタイルが適用されることがあります。 (たとえば匿名ブロックボックスは初期値である
display: inline
ではなくdisplay: block
値をもつものと解釈されます)
上述の通り、ボックスプロパティ値の計算ではカスケード計算が行われることがあります。これは要素のプロパティ値の計算とは独立した処理であると考えられます。すなわち、(container queryなどでレイアウトの結果をカスケードにフィードバックする場合を除くと)カスケードとレイアウト処理を俯瞰すると以下の順で計算されていると考えられます。
- 要素のプロパティ値を計算
-
display
値等にもとづいてボックス木を生成 - ボックスのプロパティ値を計算
- レイアウトを実行 (この時点で追加でボックスが生成されることもある)
ボックス断片
文書からボックス木が生成されたら、次はその構造をもとにレイアウト処理が行われます。レイアウトの主要な目的は、ボックスの位置と大きさを決定することにありますが、この際ボックスを画面上の連続的な領域に描画しようとすると都合が悪いことがあります。このような場合、ボックスは1つ以上のボックス断片 (box fragment) あるいは断片 (fragment)に分割されます。そのため、実際の物理的な位置は断片ごとに与えられることになります。
断片化は大きく2種類に分けられます。
- インライン分割 (inline break[4]) は、フローレイアウトコンテナ内でフロー内のインラインレベル要素がインライン方向(論理水平方向; 左右)に分割される断片化です。断片化は以下の2つの原因により発生します。
- インラインレベル要素の内部で明示的あるいは暗黙的に改行された場合。
- Bidiアルゴリズムにより複数のランが生じた場合。
-
狭義の断片化は、断片化コンテナの影響下で要素がブロック方向(論理垂直方向; 上下)に分割される断片化です。断片化コンテナには以下のようなものがあります。
- 印刷などのページメディアにおけるページ。
- 複数列レイアウトにおける列ボックス。
- 現在策定中のCSS Regionsという機能におけるリージョン。
断片化はネストした文脈で置きることもあります。そのため、ボックス断片もまた木構造を形成することになります。たとえば以下の例を考えます。
<strong><a href="https://example.com/">fragmentation container</a></strong>
ここで fragmentation
と container
の間で改行が発生したとすると、 <strong>
要素と <a>
要素はそれぞれ2つの断片に分割されることになります。 <a>
の上側の断片は <strong>
の上側の断片の子であり、 <a>
の下側の断片は <strong>
の下側の断片の子であることになります。
ボックスモデル
CSS Box Modelはボックス断片の構造を定義します。これは以下の4つの矩形からなります。
それぞれの境界部(四辺)はマージン端 (margin edge), ボーダー端 (border edge), パディング端 (padding edge), コンテント端 (content edge) と呼ばれます。また、これらのボックスの間にある領域は以下のように名前がつけられています。
- マージン領域 (margin area) はマージン端とボーダー端の間の領域 (マージンボックスからボーダーボックスを除いた領域) で、隣接要素との緩衝区間としての役割を果たします。マージンボックスは通常はボーダーボックスと同じかそれより大きいですが、負のマージンが指定された場合は大きさが逆転します。
- ボーダー領域 (border area) はボーダー端とパディング端の間の領域 (ボーダーボックスからパディングボックスを除いた領域) で、枠線状の装飾を描画するために使われます。ボーダーボックスは常にパディングボックスと同じかそれより大きいです。
- パディング領域 (padding area) はパディング端とコンテント端の間の領域 (パディングボックスからコンテントボックスを除いた領域) で、内側にある要素との緩衝区間との役割を果たします。パディングボックスは常にコンテントボックスと同じかそれより大きいです。
マージンボックス
マージンボックスは外側、つまり隣接する要素や親要素などとの関係を決定するときに使われる矩形です。そのため、マージンボックスの大きさは外寸 (outer size) と呼ばれます。
マージンボックスについて注意する必要があることはいくつかあります。
- マージンの幅は負になることがあります。その場合、この要素は実際の描画領域よりも小さいものとしてレイアウトされることになるため、インクオーバーフローしていなくても隣接要素と重なってしまう可能性があります。また、こういったケースではUIの当たり判定などの挙動も繊細なものになるため、実装時は慎重な考慮が必要になります。
- フローレイアウトでは、ブロックレベル要素のブロック方向(論理縦方向)のマージン同士が統合 (collapse) されることがあります。
- マージンの大きさには
auto
を指定することが可能ですが、この挙動はレイアウトにより異なります。
ボーダーボックス
ボーダーボックスはこの要素そのものを描画するときの、望ましい描画範囲として機能します。ただし、実際には要素の内容がこの領域の外にはみ出してしまうことがあり、これはインクオーバーフローと呼ばれます。
ボーダーボックスは以下のような目的で使われます。
パディングボックス
パディングボックスは以下のような目的で使われます。
- スクロールの基準となる領域。
- ボーダーを描画するときの、内側の端。
- ボックスの背景を塗る際のデフォルトの基準点。
ボックスのスクロールが有効なとき、当該ボックスのパディングボックスはスクロールされる側のコンテンツではなく、それを表示する窓の役割を持つ領域 (スクロールポート) を指します。スクロールされる側のコンテンツにもパディングが与えられているかのように見えますが、これはスクロール可能なオーバーフロー領域を計算するにあたって通常のパディングを模倣するように設定されているものであり、仕様上はボックスモデル上のパディング領域とは別物として定義されています。
コンテントボックス
コンテントボックスは内側、つまり子要素などとの関係を決定するときに使われる矩形です。そのため、コンテントボックスの大きさは内寸 (inner size) と呼ばれます。
コンテントボックスは以下のような目的で使われます。
- 内側の要素をレイアウトするための包含ブロック (containing block)。また、これに依存する一部のプロパティのパーセント値。
-
box-sizing: content-box
の場合の、寸法指定の基準。 - 要素の最小サイズの制約。コンテントボックスの大きさは0以上でなければならないため、コンテントボックスの大きさが0未満になってしまいそうな場合は他の制約が調整されます。
ボックスのスクロールが有効なとき、当該ボックスのコンテントボックスはスクロールされる側のコンテンツではなく、それを表示する窓の役割を持つ領域 (スクロールポート) からパディング分を取り除いた領域を指します。このような場合はスクロール方向に関するコンテントボックスの大きさにはそれほど重要な意味はないといえるでしょう。
その他の領域
-
輪郭 (outline)はボーダーと似たような機能ですが、文書の一部としてではなくWebブラウザのUIの一部としての利用が想定されています。そのため、以下のような特徴があります。
- 輪郭はレイアウトに影響しない。
- CSSでは輪郭の描画方法の詳細をあえて規定せず、Webブラウザの実装に任せている。
- ただし、一般的な実装では、ボーダーの外側にボーダーと同様の装飾が描画されます。マージンが十分に大きい場合、これはマージン領域の一部に描画されることになります。
-
インセット (inset) プロパティは位置指定レイアウトで使われる寸法です。これは
position
がabsolute
,fixed
, またはsticky
の場合は位置指定の基準となる矩形からマージンボックスまでの距離をあらわすため、マージンと近い効果を持ちます。position
がrelative
の場合は異なる効果を持ちますが、これもインセットと呼ばれます。
SVGとボックス
CSSはSVGのスタイリングにも使われますが、その性質上、SVGにおけるCSSの扱いには特殊な点がいくつかあります。たとえば、
- SVGは独自のプロパティを多数持っている一方、CSSの一般的なプロパティの中には適用されないものもあります。少なくとも、SVGを単独で実装する場合はごく限られたプロパティの実装のみで仕様を満たします。
-
display: none;
は有効ですが、それ以外のdisplay値は基本的に使われません。 -
::before
擬似要素などのボックス生成規則も基本的には使われないと考えられます。 - ただし、テキスト関連要素ではCSSのレイアウトアルゴリズムが参照されています。
いっぽう、SVGでも座標の指定などで基準となる矩形を指定する必要があることから、CSS Box ModelではSVGとの対応関係が定められています。
- fill-box は、図形のフィル領域のバウンディングボックスを指します。SVGでは、 content-box / padding-box は fill-box とみなされます。逆にSVG以外では、 fill-box は content-box とみなされます。
- stroke-box は、図形のフィル領域とストローク領域の合併のバウンディングボックスを指します。SVGでは、 border-box / margin-box は stroke-box とみなされます。逆にSVG以外では、 stroke-box は border-box とみなされます。
- view-box は特殊な指定で、要素そのものに由来する矩形ではなく、最も近いビューポートのオリジンボックスを指します。SVG以外でいうところの包含ブロックのようなものだと考えればいいかもしれません。
- 1日目: 「仕様書」
- 2日目: 「文書とボックス木」
- 3日目: 「閲覧環境 1/3」
- 4日目: 「閲覧環境 2/3」
- 5日目: 「閲覧環境 3/3」
- 6日目: 「スタイルシート 1/2」
- 7日目: 「スタイルシート 2/2」
- 8日目: 「構文 1/2」
- 9日目: 「構文 2/2」
- 10日目: 「レイアウトループ」
- 11日目: 「セレクター 1/3」
- 12日目: 「セレクター 2/3」
- 13日目: 「セレクター 3/3」
- 14日目: 「カスケード」
- 15日目: 「方向」
- 16日目: 「ボックス」
- 17日目: 「文脈」
- 18日目: 「display」
Discussion