CSS で指定されたプロパティ値が描画の際に使われる値になるまで
初めに
CSS を用いることで、ブラウザが HTML ドキュメントを画面に表示する際の体裁や見栄えを設定することができます。CSS では、property: value;
という構文で CSS プロパティの値を設定します。これをブラウザが読み取って、その指定通りに HTML 要素を画面に表示するわけです。
このとき、CSS で宣言された値は、合計 6 段階の処理を経て、最終的に画面への描画の際に用いられる値へと変換されていきます。この記事では、その各段階の処理で得られる値について詳しく解説します。
この記事を読むと、ブラウザがどのように CSS によるプロパティ値の宣言を処理しているのかが分かります。特に、以下の疑問に対する答えが見つかります。
- 同じ要素に複数の CSS がかかっている場合にどれが優先されるの?
- CSS の継承ってよく聞くけど具体的に何がどう継承されるの?
- CSS で設定しなかったプロパティってどうなるの?
- JavaScript から得られる CSS プロパティの値って実際何なの?
値の種類
概要
これから説明する 6 種類の値は、そのドキュメント内にある CSS の記述から始めて次のように順々に計算されていきます。それぞれの値の詳しい説明は、以降のそれぞれのセクションを参照してください。
宣言値
宣言値 (declared value) とは、CSS の記述によって該当要素の該当プロパティに宣言されている値です。複数個あることも存在しないこともあります。
異なる CSS 間で同じセレクタが用いられていたり、複数の異なるセレクタが 1 つの要素にマッチしたりするので、CSS によって該当要素の該当プロパティの値として宣言されるものは複数存在することがあります。この場合、宣言値は複数個になります。また、CSS によって該当要素の該当プロパティに一切値が宣言されないこともあります。この場合、宣言値は存在しません。
例えば、以下のような CSS があったとします。
span {
font-size: 12px;
}
.foo {
font-size: 15px;
}
.foo {
font-size: 18px;
}
このとき、HTML 中に <span class="foo">
と書かれた要素がもつ font-size
の宣言値は 12px
, 15px
, 18px
の 3 つになります。
カスケード値
カスケード値 (cascaded value) とは、複数個存在するかもしれない宣言値の中から実際に要素に適用する値として選ばれた値です。この複数の宣言値から実際に使う値を 1 つ選ぶ操作をカスケード処理 (cascading) と言います。そもそも宣言値が存在しない場合は、カスケード値も存在しません。
カスケード処理の概略
カスケード処理は、複数の宣言値をその優先度によって順序付けすることで行われます。最も優先度が高いものがカスケード値として採用されます。
優先度による順序付けは、以下の 3 段階で行われます。続くセクションでそれぞれの段階について詳細を述べます。
- 出自と重要度による順序付け
- セレクタの詳細度による順序付け
- ドキュメント内の出現位置による順序付け
出自と重要度
宣言値は、まずそれがどこで宣言されていたかに応じて優先付けされます。以下のリストのうち、上のものほど優先されます[1][2]。
- ブラウザによるデフォルト CSS (
!important
付き) - HTML ドキュメントから読み込まれる CSS (
!important
付き) - HTML ドキュメントから読み込まれる CSS (
!important
なし) - ブラウザによるデフォルト CSS (
!important
なし)
ここで、「HTML ドキュメントから読み込まれる CSS」とは以下の 3 種類のいずれかのことです。
-
<link>
タグによって読み込まれる外部の CSS -
<style>
タグによって HTML ファイル内に直接書かれた CSS -
style
属性によって HTML タグ内に直接書かれた CSS
このリストにおいて同じ優先度に属する宣言値が複数ある場合は、その値が宣言されたときのセレクタの詳細度が高いほど優先されます。
詳細度
セレクタの詳細度 (specificity) とは、3 つの整数の組 (A, B, C) で表され、この A, B, C は以下のように計算されます。
-
A ・・・ ID セレクタ (
#foo
の形) の個数 -
B ・・・ クラスセレクタ (
.foo
の形), 属性セレクタ ([attr="val"]
などの形), 疑似クラス (:foo
の形) の総数 -
C ・・・ 型セレクタ (
h1
,span
など), 疑似要素 (::before
など) の総数
いくつか例を挙げます。
-
*
・・・ (0, 0, 0) -
ul li +li.foo
・・・ (0, 1, 3) -
p.foo.bar
・・・ (0, 2, 1) -
.hoge.hoge
・・・ (0, 2, 0)[3] -
span#id >*[data-foo]
・・・ (1, 1, 1)
詳細度は、A, B, C の順に辞書式で大きいものが優先されます。つまり、以下のように優先度が決定されます。
- A が大きい方を優先
- A が同じ場合は B が大きい方を優先
- A と B が同じ場合は C が大きい方を優先
- A と B と C が全て同じ場合は優先度も同じ
HTML タグの style
属性内に書かれたプロパティ宣言はセレクタをもちませんが、その場合は最高の詳細度をもつことになっています。これにより、style
属性由来の宣言値は、それ以外の場所由来の宣言値より優先されます。
セレクタ詳細度まで同じ宣言値が複数ある場合は、その出現位置によってさらに優先付けされます。
出現位置
宣言値の出現位置とは、名前の通りで、HTML ファイルや CSS ファイルの中でその宣言値が宣言された場所のことです。下にあるほど優先されます。
指定値
指定値 (specified value) とは、カスケード値が存在していればそのカスケード値のことで、カスケード値が存在しなければ後述するデフォルト値のことです。カスケード値は存在しない場合がありましたが、指定値は必ず 1 つ存在します。
デフォルト値
カスケード値が存在しない場合、このセクションで説明するデフォルト値を代わりに指定値とします。デフォルト値を決定する方法はデフォルト処理 (defaulting) と呼ばれます。
各プロパティは、デフォルト処理の際に継承 (inheritence) と呼ばれる操作を行うかどうかが決まっています。例えば、font-size
は継承を行うプロパティで、margin
は継承を行わないプロパティです。
該当プロパティが継承を行う場合は、該当要素の親要素の算出値がデフォルト値になります。算出値については後述します。該当要素が <html>
だった場合は親要素がありませんが、その場合は該当プロパティに定められている初期値がデフォルト値になります。
該当プロパティが継承を行わない場合は、そのプロパティの初期値がデフォルト値になります。プロパティの初期値はプロパティごとに決まっています。
特殊なカスケード値に対する指定値
カスケード値が initial
, inherit
, unset
のいずれかであった場合、例外的に次に述べる値が指定値になります。
-
initial
・・・ 該当プロパティに定められている初期値 -
inherit
・・・ 該当要素の親要素の算出値 -
unset
・・・ カスケード値が存在しなかったものと見なして前セクションのデフォルト処理によって定まるデフォルト値
算出値
算出値 (computed value) とは、プロパティごとに定められた方法によって指定値を処理した結果の値です。概ね、指定値として許されている相対的な値設定を絶対的なものに書き換えたものが算出値になります。
例えば、長さを受け取る多くのプロパティは em
単位による宣言を許していますが、これはその要素のフォントサイズに応じて px
単位に変換されます。また、font-weight
プロパティは値に smaller
や bolder
を許していますが、これは親要素の算出値に応じて 500
や 700
などの絶対的な値に変換されます。
ただし、算出値を計算する処理は、要素を実際にレイアウトするなどの重い処理が必要ない範囲に限られます。例えば、width
プロパティは値に auto
を許しますが、それが実際にどのくらいの長さになるのかは要素をレイアウトしてみないと分かりません。そのため、width
プロパティの指定値が auto
だった場合、対応する算出値も auto
のままです。
算出値は、値の継承を行う際に使われる値です。
使用値
使用値 (used value) とは、算出値を計算する際に避けられた重い処理なども実際に行い、算出値を完全に絶対的な値にしたものです。HTML 上の各要素は、基本的にこの値に従ってブラウザによって画面上に描画されます。
例えば、width
プロパティの auto
値は、この段階で 200px
などの絶対的な値になります。
なお、一部のプロパティは、要素の種類によっては実際に描画する際に一切使われない場合があります。例えば、flex-basis
プロパティは flex 要素を描画する際にしか使われないので、flex-basis
プロパティ値を flex 要素以外がもっていても何の意味もありません。このような場合、その要素の flex-basis
プロパティに対する使用値は存在しません。
実際値
実際値 (actual value) とは、要素を描画する際に使用値をそのままでは使えない場合に、それを適切に近似した値です。使用値が存在しない場合、実際値も存在しません。
例えば、border-top-width
プロパティの使用値が 2.2px
だったとします。しかし、モニターにはピクセル単位でしか描画できないので、2.2px の罫線を実際に引くことはできません。そのため、ブラウザは代わりに 2.2px を 2px に近似して、2px の罫線を引くことになります。このときの実際値が 2px
です。
JavaScript から取得できる値
HTMLElement.style
次のように使います。
const style = element.style;
const width = style.width;
これは、該当要素の style
属性で宣言されている宣言値の一覧を返します。
返されるのはカスケード値ではなく宣言値なので、これ以外にカスケード処理で優先される宣言値があった場合、その優先された方が描画に使われます。
例えば、以下のような HTML, CSS, JavaScript があるとします。カスケード処理では、タグ中に書かれている color: red;
よりも color: blue !important;
の方が優先されるので、文字は青色で描画されます。しかし、JavaScript によって表示される値は、HTML タグ中の宣言値なので red
です。
<span id="foo" style="color: red;">foo</span>
#foo {
color: blue !important;
}
const element = document.getElementById("foo");
const style = element.style;
const color = style.color;
console.log(color);
Window.getComputedStyle
次のように使います。
const style = window.getComputedStyle(element);
const width = style.width;
関数の名前から算出値の一覧が返ってきそうですが、実は違います。実際には、プロパティの種類に応じて、算出値か使用値のいずれかが返されます。算出値が返ってくるのか使用値が返ってくるのかは、こちらを参照してください。
なお、getComputedStyle
が返す値は解決値 (resolved value) という名前がついています。すでに述べたように、解決値は算出値か使用値のどちらかで、プロパティによって変わります。
Chrome や Firefox などのデベロッパーツールの「Computed」タブで確認できる値も、この解決値です。ここでもやはり算出値ではありません。
補足
HTML では、<font color="red">
のように、CSS を使わなくても属性でスタイルを設定することができます[4]。
このような特殊な属性によって指定されたスタイルは、まず対応する CSS のプロパティの宣言に変換されます。例えば、<font color="red">
の color
属性によるスタイル設定は color: rgb(255, 0, 0);
に変換されます。その後、この宣言値は、次のいずれかであるとしてカスケード処理に通されます。
- ブラウザのデフォルト CSS 内の宣言
- セレクタ詳細度 (0, 0, 0) をもちドキュメントの一番上に置かれた宣言
どちらにしてもドキュメントから読み込まれる CSS より優先度が低くなるので、例えばそのドキュメントが font {color: blue;}
と書かれた CSS を読み込んでいれば、こちらの宣言が優先され、<font color="red">
の中身は青色で描画されます。
参考文献
この記事は、W3C の仕様書をもとにして書きました。
仕様書というと難しいイメージがありますが、W3C が出している Web 版の仕様書はクロスリンクが豊富で、知らない用語もリンクを辿ることですぐに定義を確認できるので、かなり読みやすい作りになっています。
-
仕様書では、このリストにはユーザースタイルシート (user stylesheet) というものも含まれています。これは、ブラウザのユーザーがページの見た目を調整するために設定する CSS のことですが、Chrome や Firefox などのモダンブラウザの多くはデフォルトでこの機能を提供しなくなっているので、ここでは省略しました。 ↩︎
-
仕様書では、このリストにはさらにトランジションやアニメーションによる項目が含まれています。説明が長くなるのでここでは省略しました。 ↩︎
-
この例から分かるように、同じセレクタを複数個重ねて書くと、意味的には同じですが、詳細度は上がりより優先されます。 ↩︎
-
ただし、推奨はされていません。 ↩︎
Discussion