本稿は、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のタグ (
~) で囲まれた部分に対応するノードです。
- Text はHTMLでタグなどの特別な構文を使わずに書かれた部分(※文字参照は含む)です。
-
Document はDOMのルート (最上位ノード) をあらわす特別なノードで、JavaScriptの
document
に対応します。原則としてDocumentTypeノードと最上位要素の2つの子を持ちます。
最上位ノードと最上位要素は異なることに注意してください。最上位ノードは Document のインスタンス (document
) であり、最上位要素は 要素です。
要素は HTMLHtmlElementの要素です。
DOM: 要素、タグ、タグ名
「要素」と「タグ」の区別が曖昧であれば、ここで使い分けを確認しておきましょう。たとえば以下のコード断片を考えてみます。
h1>Welcome!h1>
p>Lorem ipsum dolor sit ametp>
p>The quick fox jumps overp>
-
タグ (tag) とは
- 狭義の タグ (tag) は、
と
>
で囲まれた部分です。先ほどのソースコードの場合、タグは,
,
,
,
(2個目),
(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形式の改行に変換されます。
早期空白文字
より前の空白はパース時に除去されます。たとえば、以下のソースコードを考えます。
doctype html>
html>
head>
meta charset="utf-8">
ここで空白は以下のようにパースされます。
doctype html>html>head>⏎
␣␣␣␣meta charset="utf-8">⏎
複数行テキスト領域の特別処理
および非推奨タグである
の直後に改行が来た場合、最初の1文字に限りパース時に除去されます。
DOMよりも後に処理されるもの
CSSの規定により空白が特殊処理されるケースがいくつかあります。ここではいくつかの例を抜粋します。
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>
のようなコードがあったときに、
を展開して
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段階の仕様) ではシャドウルートが存在するときはそちらを描画し、子ノードは利用しないよう指定されています。加えて、
に割り当てられるノードがある場合は、
は割り当てられたノードに展開され、子ノードは利用されません。これはCSS Scoping等の仕様では明記されていませんが、HTML仕様にそれを推測させる記述があります。
いっぽうセレクタは、特に指定がない場合は、展開前のDOMツリーに基づいて実行されます。
CSSにおける文書木
CSSにおける 主役は「要素」 です。テキストなど、要素以外のノードには原則として直接スタイルを当てることはできません。
これは文書のルートノードであるDocumentインスタンスについても言えます。CSSの文脈では、木構造のルートは と考えます。Documentインスタンスは基本的にスタイルの適用対象ではありません。
は原則として
と
の2つの子要素のみを持ちます。このうち、
は表示しないよう規定されているため、
の表示可能な子要素は
ただひとつです。
したがって、文書全体にスタイルを当てる場合は、 html
に対して設定しても body
に対して設定しても同じように動くプロパティも多く、このようなスタイル指定が body
に対して一括指定されていることがあります。いっぽう、このような一括指定されがちなスタイルの中には、理屈の上では意図通りに作動しないはずのプロパティもあり、こういったケースに対処する特殊処理が実装されていることがあります。こういった特殊処理の中には、以下のようにCSS仕様中に明示されているものもあります。
ボックス木
CSS3で単に「ボックス (box)」と言った場合は、具体的な矩形ではなく、抽象的な描画対象を表します。
これらの抽象的なボックスは親子関係を持ち、ボックス木 (box tree) という木構造を形成します。ボックス木はいわば、DOM木を描画の都合にあわせて変形したものです。
生成されるボックス木は原則としてDOM木の構造を反映していますが、 ::before
/ ::after
擬似要素や display
プロパティーの値などに依存して異なる構造に変形されることがあります。
また、DOMのテキストノードに対応する概念はボックス木ではテキスト列 (text sequence)と呼ばれています。
ボックス木において、ボックスはDOMとの対応づけに応じて以下の3種類に分かれます。
要素に紐づくボックスと匿名ボックスの区別は、カスケードを議論するにあたって重要になります。意識しておきましょう。
まとめ
- CSSではDOMツリーの「要素」と「テキストノード」に注目してレイアウトを行う。
- これらは描画の都合に合わせて「ボックスツリー」という類似のデータ構造に変換されてから処理される。
Views: 0