🎨 CSS Advent Calendar: Day 8 / Basics of Style Resolution

Published on

Updated on

何重ものフィルタリングと計算処理を経て、適用されるスタイルが決定するまでの仕組み

Table of Contents

Table of Contents

はじめに

前回までで、Level4 時点での Cascade について一通り解説してきました。

今日からは、一旦 Cascade から離れ、 CSS の基本処理について見ていきます。

ここでは、今後の内容に直結する実際にレンダリングエンジンで CSS がどのように処理されるのか、最低限の範囲を確認しておく温度感で進めます。

実際にレンダリングエンジンで CSS がどのように処理されるのかの中でも、特に「Style Resolution」にフォーカスするため、Layout や Paint などのレンダリングの過程は、今回は割愛します。

Style Resolution

document 内の各要素に対して、ブラウザはその要素に適用されるすべての CSS プロパティに値を割り当てます。たった1つの <h1> でも 638 個 の CSS プロパティ(2025/07/19 時点:Current Property Names)が当たっており、何かしらの値を持っています。

ここで導出される「何かしらの値」は Filtering、 Cascading、Defaulting といった、 Style Resolution の一連の結果です。

<div class="card">
  <h2>タイトル</h2>
</div>
/* UA Stylesheet */
h2 { 
  font-size: 1.5em; 
  margin: 0.83em 0; 
  color: CanvasText;
}

/* Author Stylesheet */
.card h2 { 
  colr: blue;      /* typo */
  font-size: 24px; 
  margin-top: 16px;
}

h2 { 
  color: red; 
  font-weight: bold;
}

この <h2> 要素に対して、ブラウザは Filtering → Cascading → Defaulting の順で処理を行い、最終的にすべてのプロパティに値を決定します。以下のセクションで、この例を使って各段階を見ていきます。

Filtering

Filtering とは、CSS 仕様上の「CSS 文法上の誤りがあるものを排除する」ことを、主たる目的とする過程です。

ブラウザの実装では、パーサがトークンを読み進める段階で、プロパティ名の検証、値の妥当性チェックなどを行います。例えば、Chromium では CSS パーサが各宣言を解析する際に、プロパティのメタデータ(受け入れ可能な値の型、初期値など)と照合して妥当性を判断しています。

ここでのチェックには、known property name(文法的に正しいプロパティ名)に match するかどうかやプロパティとして正しいかどうかのほかに、「そもそも Style Sheet に入っているか」や「Style Rule に包含されているか」などのチェックもあります。要は 「CSS 的に正しいかどうか静的に確認」する処理です。その結果として、値が declared value として抽出され、適用外の値は invalid at parse-time となります。

/* 有効値が declared values として残る*/

/* UA Stylesheet からの declared values */
h2 { 
  font-size: 1.5em;
  margin: 0.83em 0;
  color: CanvasText;
}

/* Author Stylesheet からの declared values */
.card h2 { 
  /* colr: blue;      invalid at parse-time として無視される(プロパティ名が不正) */
  font-size: 24px;
  margin-top: 16px;
}

h2 { 
  color: red;
  font-weight: bold;
}

Filtering の結果、単一の要素に適用可能な「declared value のリスト」が得られます。

Cascading

Filtering の結果、単一の要素に対して複数の宣言が適用された場合、Cascading でその競合を解決しなければなりません。

/* 競合する declared values list */
/* 競合解決の末生き残るものが、ascaded values */

/* font-size: 1.5em;    UA Stylesheet */
font-size: 24px;     /* Author Stylesheet (.card h2) */

/* color: CanvasText;   UA Stylesheet */
color: red;          /* Author Stylesheet (h2) */

Author/User/UA Stylesheet、その中でもインラインスタイルや @import でのスタイル読み込みなど、さまざまなソースからすべてのスタイルを集約し、最終的に唯一の宣言を採用する必要があります。Author StyleSheet で何も指定していなくとも、ほとんどのブラウザで UA StyleSheet が適用されていることを鑑みると、この過程は必須と言っても良いでしょう。

Cascading の過程で、単一の宣言を採用するアルゴリズムが、 Cascading Sorting Order です。こうして宣言を単一に絞ることで、後続の Value Processing において値を単一に絞って計算していくことができます。Cascading の結果、単一に絞られた値は、cascaded value となります。

Cascade に関しては、このアドベントカレンダーでも多分に触れてきました。詳細は、Day3~Day5 を参照して下さい。

Defaulting

Cascading の結果として cascaded value が得られましたが、すべてのプロパティが cascaded value を持つとは限りません。例えば、Author/User/UA Stylesheet で宣言されていないプロパティは、cascaded value を持ちません。

/* e.g, cascaded value が存在しないプロパティ */
display: ?;     
font-family: ?; 
line-height: ?; 

しかし、レンダリングを行うためには、すべての要素のすべてのプロパティが必ず値を持つ必要がありますDefaulting は、cascaded value が存在しない場合に適切な値を決定する過程です。

/* Defaulting の結果 */

/* cascaded value のまま */
font-size: 24px;     
margin: 0.83em 0;    
margin-top: 16px;    
color: red;          
font-weight: bold;   

display: block;      /* UA Stylesheet で設定される(h2 の場合) */
font-family: serif;  /* initial value */
line-height: normal; /* initial value */

Initial Values

すべての CSS プロパティには initial value が定義されています。これは、そのプロパティが一切宣言されていない場合のデフォルト値です。

例えば:

Inheritance

CSS のプロパティは、inherited propertynon-inherited property(仕様にはないが、敢えて対照的に書く) の 2 つに分類されます。

Inherited property

/* 親要素 */
.parent {
  color: blue; /* inherited property */
}

/* 子要素には color の宣言がない → 親の blue を継承 */
.child {
  /* color は自動的に blue になる */
}

Non-inherited property の場合:

/* 親要素 */
.parent {
  border: 1px solid black; /* non-inherited property */
}

/* 子要素には border の宣言がない → initial value (none) を使用 */
.child {
  /* border は自動的に none になる */
}

Value Processing

ここまで、Filtering → Cascading → Defaulting と値が処理されてきました。仕様では、これらの処理を含めた値の変遷を Value Processing としてまとめています。

CSS の値は、以下の 6 つの段階を経て最終的な表示値に変換されます。

1. Declared Values

要素に適用されるすべてのプロパティ宣言を収集した段階の値。Filtering の結果。 1 つの要素に対して、0 個以上の宣言値が存在する可能性がある。

/* 複数の宣言値が存在する例 */
p { color: blue; }  /* 宣言値 1 */
.text { color: red; }  /* 宣言値 2 */
#content p { color: green; }  /* 宣言値 3 */

2. Cascaded Value

Cascading によって、複数の宣言値から 1 つが選ばれた値。各プロパティごとに、最大で 1 つの cascaded value が存在する。

/* 上記の例で、#content p セレクタが最も詳細度が高いので... */
/* cascaded value: green */

3. Specified Value

Defaulting を経て、すべてのプロパティに値が割り当てられた段階。この段階で、すべての要素のすべてのプロパティが必ず 1 つの値を持つ。

4. Computed Value

親との相対的な値や依存関係を解決した値。この段階での主な処理は以下のようになる。

.parent { font-size: 16px; }
.child { 
  font-size: 1.5em; /* specified: 1.5em → computed: 24px */
  width: 50%; /* specified: 50% → computed: 50%(親のサイズはまだ不明)*/
}

5. Used Value

Layout を完了して実際に利用される値。

.parent { width: 800px; }
.child { 
  width: 50%; /* computed: 50% → used: 400px */
  height: auto; /* computed: auto → used: 実際の高さ(e.g. 200px)*/
}

要素にプロパティが適用される場合のみ、used value が存在する。例えば、display: inline の要素には width の used value は存在しない。

6. Actual Value

表示環境の制約を考慮した最終的な値。

.element {
  border-width: 0.5px; /* used: 0.5px → actual: 1px(デバイスが対応していない場合)*/
  opacity: 0.999; /* used: 0.999 → actual: 1(精度の制限など)*/
}

Phew! Sum Up!

今回は、以下の 5 段階の処理を解説しました。

  1. Declared → Cascaded: 複数の宣言から 1 つを選択
  2. Cascaded → Specified: すべてのプロパティに値を割り当て
  3. Specified → Computed: 親との相対値を絶対化、IACVT の導出など
  4. Computed → Used: window などを含めた Layout に基づく計算
  5. Used → Actual: 環境的な制約への適応をさせる

競合解決や値の補完などを行いながら、異なる環境やコンテキストで最適な表示を実現する過程を経て、ブラウザを介して我々の目にみえる形で CSS が適用されているわけです。

Appendix