CSS大解剖 2日目: 「文書とボックス木」
本稿は、2024年2月頃に書き溜めていたシリーズです。最後まで温存させるのが勿体ないので、未完成ですがそのまま公開します(公開日: 2025/9/21)。そのため、内容の重複や記述方針の不一致があるかもしれませんが、ご理解ください。
CSSの仕様を理解するために、1日ごとにテーマを決めて説明する企画2日目です。今日のテーマは「文書とボックス木」です。
文書
CSSの処理モデルにおいて、 文書 (document) とはCSSのスタイルが適用される対象のことです。これはほとんどの場合HTML文書なので、以降では文書はHTML文書のことであるとします。
DOM
HTML文書の形式的な構造はDOMで規定されています。DOMにおいて、文書はノードの木構造として定義されています。
ノードはNodeクラスのインスタンスですが、Nodeには様々なサブクラスがあります。
この中でも重要なのが Element, Text, Document の3種類です。
-
Element はHTMLのタグ (
<xxx>
~</xxx>
) で囲まれた部分に対応するノードです。 - Text はHTMLでタグなどの特別な構文を使わずに書かれた部分(※文字参照は含む)です。
-
Document はDOMのルート (最上位ノード) をあらわす特別なノードで、JavaScriptの
document
に対応します。原則としてDocumentTypeノードと最上位要素の2つの子を持ちます。
最上位ノードと最上位要素は異なることに注意してください。最上位ノードは Document のインスタンス (document
) であり、最上位要素は <html>
要素です。 <html>
要素は HTMLHtmlElementの要素です。
DOM: 要素、タグ、タグ名
「要素」と「タグ」の区別が曖昧であれば、ここで使い分けを確認しておきましょう。たとえば以下のコード断片を考えてみます。
<h1>Welcome!</h1>
<p>Lorem ipsum dolor sit amet</p>
<p>The quick fox jumps over</p>
-
タグ (tag) とは
- 狭義の タグ (tag) は、
<
と>
で囲まれた部分です。先ほどのソースコードの場合、タグは<h1>
,</h1>
,<p>
,</p>
,<p>
(2個目),</p>
(2個目) の6つです。- タグはHTMLソースコード上の概念であり、DOM上の概念としては存在しません。
-
タグ名 (tag name) のことをタグと呼ぶこともあります。先ほどのソースコードの場合、コード中に出現するタグ名は
h1
とp
の2種類です。
- 狭義の タグ (tag) は、
- 要素 (element) は、大まかには、対応する開きタグと閉じタグで囲まれた部分のことです。先ほどのソースコードの場合、要素は3つあります。
DOM: 空白の扱い
HTML文書においては空白 (スペース文字、タブ、改行など) がある程度特別扱いされています。これは、DOMより前に処理されているものとDOMより後に処理されているものに大別できます。
DOMより前に処理されるもの
DOMになった時点で元のデータが失われているので、CSSでどう設定しても元の状態を復元することはできません。
なお、JavaScriptでテキストノードを直接追加した場合はこれらの処理の影響を受けません。たとえば、この方法を使えばU+000D Carriage Returnを含むテキストノードを作ることもできます。
改行の正規化
HTMLソースコード中に直接出現する改行文字は正規化されます。いわゆるCRやCRLF形式の改行はLF形式の改行に変換されます。
早期空白文字
<head>
より前の空白はパース時に除去されます。たとえば、以下のソースコードを考えます。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
ここで空白は以下のようにパースされます。
<!doctype html><html><head>⏎
␣␣␣␣<meta charset="utf-8">⏎
複数行テキスト領域の特別処理
<pre>
, <textarea>
および非推奨タグである <listing>
の直後に改行が来た場合、最初の1文字に限りパース時に除去されます。
DOMよりも後に処理されるもの
CSSの規定により空白が特殊処理されるケースがいくつかあります。ここではいくつかの例を抜粋します。
-
white-space属性の値が
normal
,nowrap
,pre-line
のいずれかの場合は、空白をまとめたり除去したりする処理が適用されます。 - レンダリング時はCR改行はスペース文字と同等として扱う、タブはtab-size属性の規定にもとづき処理されるなど、特別扱いされる空白がいくつかあります。
- テーブルレイアウトの規定ではan anonymous inline containing only white spacesという記述により、ソースコード中の不要な空白テキストを適切に無視するように規定しています。
DOM: シャドウ木
DOMの要素はいわゆる「子ノード」とは別に(ノードに関連づけられた)シャドウルート (shadow root)を持つことがあります。シャドウルートは主にWeb Componentsを実現するための要素技術で、たとえば
<custom-accordion>
<div slot="summary">今日のごはん</div>
<ul slot="details">
<li>ごはん</li>
<li>鶏もも肉の照り焼き</li>
<li>もやしのナムル</li>
<li>玉ねぎのバター炒めスープ</li>
</ul>
</custom-accordion>
のようなコードがあったときに、 <custom-accordion>
を展開して
<details class=".custom-accordion-details">
<summary class=".custom-accordion-summary"><div>今日のごはん</div>
<ul>
<li>ごはん</li>
<li>鶏もも肉の照り焼き</li>
<li>もやしのナムル</li>
<li>玉ねぎのバター炒めスープ</li>
</ul>
</details>
のように描画するといったことを可能にします。
このときに問題になるのが、「展開前のDOMツリー」と「展開後のDOMツリー」をどう共存させるかという問題です。ここで、展開前のDOMツリーは「子ノード」として、展開後のDOMツリーは「関連づけられたシャドウルート」として表現します。
このようなケースでは、実際に描画したいのは展開後のDOMツリーです。そこで、CSS Scoping Module (※まだWorking Draft段階の仕様) ではシャドウルートが存在するときはそちらを描画し、子ノードは利用しないよう指定されています。加えて、 <slot>
に割り当てられるノードがある場合は、 <slot>
は割り当てられたノードに展開され、子ノードは利用されません。これはCSS Scoping等の仕様では明記されていませんが、HTML仕様にそれを推測させる記述があります。
いっぽうセレクタは、特に指定がない場合は、展開前のDOMツリーに基づいて実行されます。
CSSにおける文書木
CSSにおける 主役は「要素」 です。テキストなど、要素以外のノードには原則として直接スタイルを当てることはできません。
これは文書のルートノードであるDocumentインスタンスについても言えます。CSSの文脈では、木構造のルートは <html>
と考えます。Documentインスタンスは基本的にスタイルの適用対象ではありません。
<html>
は原則として <head>
と <body>
の2つの子要素のみを持ちます。このうち、 <head>
は表示しないよう規定されているため、 <html>
の表示可能な子要素は <body>
ただひとつです。
したがって、文書全体にスタイルを当てる場合は、 html
に対して設定しても body
に対して設定しても同じように動くプロパティも多く、このようなスタイル指定が body
に対して一括指定されていることがあります。いっぽう、このような一括指定されがちなスタイルの中には、理屈の上では意図通りに作動しないはずのプロパティもあり、こういったケースに対処する特殊処理が実装されていることがあります。こういった特殊処理の中には、以下のようにCSS仕様中に明示されているものもあります。
ボックス木
CSS3で単に「ボックス (box)」と言った場合は、具体的な矩形ではなく、抽象的な描画対象を表します。
これらの抽象的なボックスは親子関係を持ち、ボックス木 (box tree) という木構造を形成します。ボックス木はいわば、DOM木を描画の都合にあわせて変形したものです。
生成されるボックス木は原則としてDOM木の構造を反映していますが、 ::before
/ ::after
擬似要素や display
プロパティーの値などに依存して異なる構造に変形されることがあります。
また、DOMのテキストノードに対応する概念はボックス木ではテキスト列 (text sequence)と呼ばれています。
ボックス木において、ボックスはDOMとの対応づけに応じて以下の3種類に分かれます。
- 特定の要素に紐付くボックス
- 主ボックス (principal box) ... 要素ごとに最大1つ存在する。
- 主ボックス以外の、要素に紐付くボックス (例:
display: list-item
によって生成されるmarker box)
- 匿名ボックス (anonymous box) ... 特定のボックスに紐付かないボックス (例: 匿名インラインボックス あるいはドラフト仕様ではルートインラインボックスと呼ばれるダミーのボックス)
要素に紐づくボックスと匿名ボックスの区別は、カスケードを議論するにあたって重要になります。意識しておきましょう。
まとめ
- CSSではDOMツリーの「要素」と「テキストノード」に注目してレイアウトを行う。
- これらは描画の都合に合わせて「ボックスツリー」という類似のデータ構造に変換されてから処理される。
Discussion