💬

CSS大解剖 2日目: 「文書とボックス木」

に公開

本稿は、2024年2月頃に書き溜めていたシリーズです。最後まで温存させるのが勿体ないので、未完成ですがそのまま公開します(公開日: 2025/9/21)。そのため、内容の重複や記述方針の不一致があるかもしれませんが、ご理解ください。


CSSの仕様を理解するために、1日ごとにテーマを決めて説明する企画2日目です。今日のテーマは「文書とボックス木」です。

文書

CSSの処理モデルにおいて、 文書 (document) とはCSSのスタイルが適用される対象のことです。これはほとんどの場合HTML文書なので、以降では文書はHTML文書のことであるとします。

DOM

HTML文書の形式的な構造はDOMで規定されています。DOMにおいて、文書はノードの木構造として定義されています。

ノードはNodeクラスのインスタンスですが、Nodeには様々なサブクラスがあります。

図: Nodeのクラス図。Document, Text, Elementが最も重要

この中でも重要なのが Element, Text, Document の3種類です。

  • Element はHTMLのタグ (<xxx></xxx>) で囲まれた部分に対応するノードです。
  • Text はHTMLでタグなどの特別な構文を使わずに書かれた部分(※文字参照は含む)です。
  • Document はDOMのルート (最上位ノード) をあらわす特別なノードで、JavaScriptの document に対応します。原則としてDocumentTypeノードと最上位要素の2つの子を持ちます。

最上位ノードと最上位要素は異なることに注意してください。最上位ノードは Document のインスタンス (document) であり、最上位要素は <html> 要素です。 <html> 要素は HTMLHtmlElementの要素です。

図: Documentの下にhtml要素がある

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) のことをタグと呼ぶこともあります。先ほどのソースコードの場合、コード中に出現するタグ名は h1p の2種類です。
  • 要素 (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の規定により空白が特殊処理されるケースがいくつかあります。ここではいくつかの例を抜粋します。

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種類に分かれます。

要素に紐づくボックスと匿名ボックスの区別は、カスケードを議論するにあたって重要になります。意識しておきましょう。

まとめ

  • CSSではDOMツリーの「要素」と「テキストノード」に注目してレイアウトを行う。
  • これらは描画の都合に合わせて「ボックスツリー」という類似のデータ構造に変換されてから処理される。

Discussion