はじめに
CSSを書いたことがある多くの人が、「指定したCSSが正しいはずなのに適用されていない」という状態に頭を悩ませたことがあると思います。
これは「適用したいCSSの指定」よりも、「別のCSSの指定」が優先されてしまうために起こります。
この、「どちらの指定を優先するか」に関わるのが「詳細度」です。
また、「CSSを指定していないはずなのに、見た目が変わっている」ということもあります。
これは、親要素に適用した指定が子要素にも引き継がれる「継承」が関わっています。
今まで詳細度と継承についてふんわりとした理解に留めていたのですが、改めて整理してまとめてみました。
詳細度とは
CSSの「詳細度」とは、CSSの指定の「重み」を計算するアルゴリズムです。
同じ要素に対して複数のCSSの指定があった場合、この「重み」が最も大きい指定が適用されます。
例えば、インライン記述での指定とクラスセレクタの指定ではインライン記述の方が重みが大きいため、インライン記述の指定の指定が優先されます。
インライン記述での指定 > クラスセレクタの指定
See the Pen CSS詳細度の比較 by かべちよ (@kabechiyo13) on CodePen.
基本の詳細度
詳細度の重みは以下のような順番になっています。
-
最も重い
!important
-
インライン記述 (
style="background: red"
) -
ID列
- IDセレクタ (
#name
)
- IDセレクタ (
-
CLASS列
- クラスセレクタ(
.button
) - 属性セレクタ(
[type="text"]
など) - 擬似クラスセレクタ(
:hover
など:not()
,:is()
,:where()
は例外)
- クラスセレクタ(
-
TYPE列
- 要素セレクタ(
div
など) - 擬似要素セレクタ(
::before
など)
- 要素セレクタ(
-
影響を与えない
- ユニバーサルセレクタ(
*
) - 結合子(
+
,>
,~
,||
) - 一部の擬似クラス(
:not()
,:is()
)
- ユニバーサルセレクタ(
!important > インライン記述 > ID列 > CLASS列 > TYPE列
詳細度の重みが同じ場合は、より後に指定されたスタイルが適用されます。
詳細度の計算と重みの比較
1つしかセレクタを指定していない時は、そのまま重みを比較することができます。
しかし、CSSでは複数のセレクタを同時に指定することができ、これによって詳細度の重みが変わってきます。
こうした場合の詳細度の比較方法について見ていきます。
詳細度の計算
詳細度には、ID列
、CLASS列
、TYPE列
という3つの要素があり、最終的な重みはそれぞれの列のセレクタの数によって決まります。
ID-CLASS-TYPE
の形で詳細度を表し、各列に一致するセレクタがあるごとに、数値を追加していきます。
-
ID列
- 一致するセレクタごとに重みの値に
1-0-0
を追加する
- 一致するセレクタごとに重みの値に
-
CLASS列
- 一致するセレクタごとに重みの値に
0-1-0
を追加する
- 一致するセレクタごとに重みの値に
-
TYPE列
- 一致するセレクタごとに重みの値に
0-0-1
を追加する
- 一致するセレクタごとに重みの値に
例えば、#name:not(input).container ::before
の場合、ID列が1つ、CLASS列が1つ(:not()
は例外)、TYPE列が2つなので重みの値は1-1-2
になります。
詳細度の重みの比較
ID-CLASS-TYPE
が出せたら、重みの比較をしていきます。
重みの比較は列ごとに行われ、ID列 -> CLASS列 -> TYPE列の順で比較していきます。
先の列で優劣がつけば、それ以降の列の値は関係ありません。
(mermaid使ってみたかった)
例えば、A2-0-0
という重みの値の指定とB1-2-1
という重みの値の指定の場合、ID列でAが勝ちます。
C0-2-1
という重みの値の指定とD0-2-2
という重みの値の指定の場合は、TYPE列でDが勝ちます。
詳細度の計算・比較をしてくれる便利ツール(Specificity Calculator)
迷った時は、詳細度を計算し重みを比較してくれる便利なツールもあります。
セレクタの指定を入力すると計算結果を表示してくれ、右上の「Sort by specificity」をクリックすると重い順に並べ替えてくれます。
例外
詳細度の比較をする時の例外として、!important
やインライン記述
があります。
インライン記述
インライン記述はスタイルを上書きするため、常に詳細度が最も高くなります。
インライン記述を上書きするには、後述の!important
を使うしかありません。
CSSの保守性を下げ後々の魔窟を招くのでできるだけ使用しないようにしましょう。
!important
例外的に!important
はインライン記述で指定されたスタイルを上書きすることができます。
!important
は上書きがほぼ不可能なため、CSSの保守性を下げてしまいます。
そのため、使用をできるだけ避けるべきとされています。
もしどうしても使用が必要なコメントでその理由を記載しておきましょう。
:is()
, :not()
擬似クラスの:is()
, :not()
自体は重みに影響を与えませんが、()
内に指定されたセレクタは重みを計算します。
例えば、:is(p)
や:not(p)
の場合は()内が計算されp
と同じく重みが0-0-1
になります。
()内に複数のセレクタが入る場合は、最も詳細度が高いものになります。
例えば:is(#name, .text, p)
の指定は詳細度が1-0-0
になるため、同じ詳細度の#name
では上書きできますが、.text(0-1-0
)やp(0-0-1
)では上書きできません。
See the Pen :is()の詳細度 by かべちよ (@kabechiyo13) on CodePen.
ちなみに、:not(#fakeID)を追加してID列のセレクタを1増やすと上書きすることができます。
See the Pen :is()の詳細度を:not(#fakeID)で上書き by かべちよ (@kabechiyo13) on CodePen.
:where()
:where()
とは、:is()
と同じく複数のセレクタを1つにまとめられる疑似要素です。
:where()
を指定すると、常に詳細度が0-0-0
に置き換えられます。
CSSの継承
詳細度のほかに、CSSには「継承」という概念があり、一部のプロパティは指定がない場合に親要素の指定を引き継いで反映します。
もし指定した覚えのないスタイルが要素に適用されている場合は異なるセレクタの指定の他に、親要素からの継承の可能性もあります。
CSSプロパティは継承されるものとされないものがあり、それぞれ「継承プロパティ」、「非継承プロパティ」と呼ばれます。
-
継承プロパティ
- 親要素に指定があり、要素にそのプロパティが指定されていない場合は、親要素の指定が引き継いで反映される
- 例:
color
,font-family
,font-size
,line-height
,list-style
,text-align
,word-break
-
非継承プロパティ
- 親要素に指定があり、 要素にそのプロパティが指定されていない場合は、プロパティの初期値が反映される
- 例:
border
,background
,margin
,padding
,width
,height
ただし、ユーザーエージェントによる指定がある場合はプロパティに値が指定されていなくても親要素からの継承はされず、ユーザーエージェントによる指定が反映されます。
より多くのプロパティの継承の有無はこちらのサイトにまとまっていました。
もし、非継承プロパティを親要素から継承したい場合は、inherit
という値をプロパティに指定します。
逆に親要素を継承せず、初期値を指定したい場合はinitial
という値をプロパティに指定します。
上書きしたい場合は、要素に指定を追加すれば上書きされます。
See the Pen 継承 by かべちよ (@kabechiyo13) on CodePen.
詳細度の調整によってCSSを上書きする方法
ここまで、詳細度とは何かや詳細度の計算方法、重みの比較方法、継承などを見てきました。
では、実際に指定したいCSSが詳細度で負けてしまったときの対処法を紹介していきます。
前提
- なるべく
#id
を使わない - インライン記述はできる限り避ける
-
!important
は使わない - 子孫セレクタや擬似要素、擬似クラスでネストを増やしすぎない
これらの指定は詳細度の重みを重くし上書きすることを困難にしたり、詳細度を複雑にする原因になります。
なるべくこうした指定は避け、どうしても必要な場合は適宜コメントを追加しましょう。
基本の手順
- CSSを指定したい要素で、上書きしたい指定の詳細度を確認する
- 詳細度を調整する
a. 上書きしたいセレクタの詳細度を下げる
b. 指定したいセレクタに同じ詳細度を与え、上書きする指定の後ろに記述する
c. 指定したいセレクタにより強い詳細度を与える
もし上書きしたいセレクタの詳細度をリファクタリングによって下げられるのであれば、一つの手段です。
また、上書きしたいセレクタより後ろに指定したいセレクタを記述できるのであれば、同じ詳細度を指定することで上書きすることができます。
それが困難あれば、上書きしたいセレクタよりも大きい詳細度を与えることで上書きできます。
詳細度を大きくする方法
具体的に詳細度をあげたいときの方法をいくつか紹介します。
セレクタを追加する
例えば以下のような状態でcolor: blue
を指定したい場合を考えます。
See the Pen 詳細度 by かべちよ (@kabechiyo13) on CodePen.
.list > li
は0-1-1
、.listItem
は0-1-0
です。
CLASS列
を1増やすか、.listItem
の指定の方が後ろに記述されているのでTYPE列
を1増やして同じ重みにすることで勝つことができそうです。
試しにCLASS列
である属性セレクタの[class]
の指定を追加してみると、無事青色になりました。
See the Pen 詳細度を上げる方法-セレクタの追加 by かべちよ (@kabechiyo13) on CodePen.
セレクタを複製する
また、同じセレクタを複製しても詳細度の重みを大きくすることができます。
試しに、CLASS列
であるクラスセレクタの.listitem
を複製してを1つ増やしてみると、青色が反映されました。
セレクタの複製を乱用すると、詳細度が複雑になる原因になるので、なるべく使わないようにしましょう
See the Pen 詳細度を上げる方法-セレクタの複製 by かべちよ (@kabechiyo13) on CodePen.
[IDセレクタ
を上書きする場合]存在しないIDと:not()
を使う
これはMDNで紹介されていて知った方法なのですが、#fakeID
という存在しないid
を使うことで詳細度を上げる方法もあるようです。
IDセレクタ
でスタイルが指定されている場合、上書きするにはIDセレクタ
を追加するかインライン記述や!important
を使わざるを得ませんが、保守性のためにはどれも避けたい方法です。
そうした場合に、:not(#fakeID)
という形で指定をすれば、少なくとも新しいIDを追加はせずにすみます。
(IDセレクタの指定は増えてしまってはいますが)
詳細度が複雑になるという欠点はあるので、そもそもIDセレクタの指定をリファクタリングができるのであればそちらを検討したいですね。
See the Pen 詳細度を上げる方法-:not(#fakeID) by かべちよ (@kabechiyo13) on CodePen.
Ref