CSSセレクタの詳細度と!important
OOCSSやSMACSS、FLOCSSやITCSSのようなCSS設計の手法はCSSの仕様を踏まえた上で考えられています。
ですが、自分はCSSの仕様について理解がおよんでいるんだろうか?と疑問が湧いたので、CSSのセレクタについて調べてみました。
CSSの基本的な構文とカスケーディング
CSSの基本的な構文はセレクタとブレース、プロパティ名と値からなっています。以下の例では.foo
はセレクタ、{}
はブレース、display:
はプロパティ名、block;
は値です。
#foo { display: block; }
CSSの構文自体はとてもシンプルで分りやすいものですが、同じセレクタ(この場合は.foo
)を続けて指定すると後から指定したものが優先されます。これがカスケーディングと呼ばれるものです。
#foo { display: block; } /* こちらが優先される */ #foo { display: none; }
カスケーディングはあとから指定したセレクタの方が詳細度と呼ばれる強さが同等かそれ以上の場合に起きます。例えば以下の例では同じ要素を選択してはいますが、詳細度を弱くして指定しているので上書きされません。
/* こちらが優先される */ #foo { display: block; } [id="foo"] { display: none; }
詳細度とはなにか?
詳細度(Specificity)とはセレクタの強さを決めるための仕組みのことです。
- 0, 0, 0, 0 - 全称セレクタ、結合子、
:not()
疑似クラス - 0, 0, 0, 1 - 要素セレクタ、疑似要素
- 0, 0, 1, 0 - クラスセレクタ、属性セレクタ
- 0, 1, 0, 0 - IDセレクタ、
:not()
以外の疑似クラス - 1, 0, 0, 0 - style属性(インラインスタイル)
詳細度は4つの数値(0,0,0,0)からなり、左から判定されます。例えばクラスセレクタ(.foo
)の詳細度は0,0,1,0ですが、IDセレクタ(#foo
)は0,1,0,0の詳細度を持っているので、IDセレクタが優先して適応されます。
クラスセレクタはIDセレクタに詳細度で勝つことはできません。上書きしようとすると.foo #foo
のように詳細度を強くする(この場合は0,1,1,0)か、同じIDセレクタを上書きしたい箇所よりあとに記述する必要があります。
結合子、擬似要素、擬似クラス
セレクタ名 | 構文 | 詳細度 | 説明 |
---|---|---|---|
全称セレクタ | * |
(0, 0, 0, 0) | すべての要素 |
要素セレクタ | E |
(0, 0, 0, 1) | E要素 |
構造擬似クラス | :root |
(0, 0, 1, 0) | 文書のルート要素(HTML文書ではhtml要素)にマッチ |
クラスセレクタ | .foo |
(0, 0, 1, 0) | class属性がfooの要素 |
属性セレクタ | [attr] |
(0, 0, 1, 0) | attr属性をもつ要素 |
属性セレクタ | [attr="value"] |
(0, 0, 1, 0) | attr属性の値がvalueである要素 |
IDセレクタ | #foo |
(0, 1, 0, 0) | id属性がfooの要素 |
上記は基本的なセレクタの指定と詳細度をまとめたものです。しかし、.foo .bar
のように複数のセレクタを指定したり、擬似要素や擬似クラスを指定した場合には詳細度を考慮する必要があります。
以下は代表的な結合子、擬似要素、擬似クラスをまとめたものです。結合子は複数の要素で指定する場合、擬似要素はCSSでHTML内に要素を生成するようなイメージ、擬似クラスは選択する条件を狭めたものと考えるとよさそうです。
括弧で囲んでいないものは特定のセレクタ自身の詳細度です。例えばE > F
の場合はE
とF
は要素セレクタなので詳細度は合計すると(0, 0, 0, 2)となりますが、>
自身は詳細度を上げることはないので0, 0, 0, 0となっています。
セレクタ名 | 構文 | 詳細度 | 説明 |
---|---|---|---|
結合子(子孫セレクタ) | E F |
0, 0, 0, 0 (0, 0, 0, 2) | E要素の子孫要素であるすべてのF要素 |
結合子(子セレクタ) | E > F |
0, 0, 0, 0 (0, 0, 0, 2) | E要素の直接の子要素であるすべてのF要素 |
結合子(隣接セレクタ、隣接兄弟セレクタ) | E + F |
0, 0, 0, 0 (0, 0, 0, 2) | E要素の直後にあらわれるF要素 |
結合子(間接セレクタ、一般兄弟セレクタ) | E ~ F |
0, 0, 0, 0 (0, 0, 0, 2) | E要素の後にあらわれるF要素 |
否定擬似クラス | E:not(x) |
0, 0, 0, 0 (0, 0, 0, 2) | xにマッチしないE要素 |
擬似要素 | E::first-line |
0, 0, 0, 1 (0, 0, 0, 2) | E要素の実際に表示されている最初の行 |
擬似要素 | E::first-letter |
0, 0, 0, 1 (0, 0, 0, 2) | E要素の最初の文字(contentプロパティを指定している::before擬似要素が生成される場合は生成される要素に適応される) |
擬似要素 | E::before |
0, 0, 0, 1 (0, 0, 0, 2) | 要素の前に最初の子要素となるインラインの擬似要素を生成する |
擬似要素 | E::after |
0, 0, 0, 1 (0, 0, 0, 2) | E要素の後に最後の子要素となるインラインの擬似要素を生成する |
属性セレクタ | [attr^="value"] |
0, 0, 1, 0 (0, 0, 1, 0) | attr属性の値がvalueで始まる(前方一致)要素 |
属性セレクタ | [attr$="value"] |
0, 0, 1, 0 (0, 0, 1, 0) | attr属性の値がvalueで終わる(後方一致)要素 |
属性セレクタ | [attr*="value"] |
0, 0, 1, 0 (0, 0, 1, 0) | attr属性の値がvalueを含む(部分一致)要素 |
属性セレクタ | [attr~="value"] |
0, 0, 1, 0 (0, 0, 1, 0) | attr属性の値がスペースで区切られた項目リストであり、値がvalueである要素 |
属性セレクタ | `[attr | ="value"]` | 0, 0, 1, 0 (0, 0, 1, 0) | attr属性の値がハイフンで区切られた項目リストであり、そのうちの値の1つが正確にvalueである要素 |
言語疑似クラス | E:lang(C) |
0, 0, 1, 0 (0, 0, 1, 1) | 言語情報CをもつE要素 |
構造擬似クラス | E:first-of-type |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ最初のE要素 |
構造擬似クラス | E:last-of-type |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ最後のE要素 |
構造擬似クラス | E:nth-of-type(x) |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつx番目のE要素 |
構造擬似クラス | E:nth-of-type(odd) |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ奇数番目のE要素 |
構造擬似クラス | E:nth-of-type(even) |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ偶数番目のE要素 |
構造擬似クラス | E:nth-of-type(-n+x) |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ最初のx番目のE要素 |
構造擬似クラス | E:nth-of-type(n+2) |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ最初のE要素以外 |
構造擬似クラス | E:nth-last-of-type(x) |
0, 0, 1, 0 (0, 0, 1, 1) | 同じ型をもつ最初から数えてx番目のE要素 |
構造擬似クラス | E:empty |
0, 0, 1, 0 (0, 0, 1, 1) | 子要素や文字(スペースも含)をもたない空の要素(コメント構文は判定されない) |
ロケーション疑似クラス | E:link |
0, 0, 1, 0 (0, 0, 1, 1) | E要素内のすべてのリンク |
ロケーション疑似クラス | E:visited |
0, 0, 1, 0 (0, 0, 1, 1) | E要素内の訪問済みのリンク |
ロケーション疑似クラス | E:target |
0, 0, 1, 0 (0, 0, 1, 1) | ハッシュ(#)が指定されているURLが参照した識別子と一致するid属性をもつ唯一の要素 |
ユーザーアクション擬似クラス | E:hover |
0, 0, 1, 0 (0, 0, 1, 1) | マウスオーバーしたE要素 |
ユーザーアクション擬似クラス | E:active |
0, 0, 1, 0 (0, 0, 1, 1) | マウスを押して離すまでの間か、タブキーでフォーカスしたE要素 |
ユーザーアクション擬似クラス | E:focus |
0, 0, 1, 0 (0, 0, 1, 1) | キーボードで選択するか、マウスでアクティベート(フォームの入力など)をしたE要素 |
インプット疑似クラス | E:enabled |
0, 0, 1, 0 (0, 0, 1, 1) | disabled属性が指定されていない使用可能なE要素 |
インプット疑似クラス | E:disabled |
0, 0, 1, 0 (0, 0, 1, 1) | disabled属性が指定されている使用不可能なE要素 |
インプット疑似クラス | E:checked |
0, 0, 1, 0 (0, 0, 1, 1) | チェックかon状態にされているE要素(ラジオ、チェックボックス、オプションボタン) |
インプット疑似クラス | E:required |
0, 0, 1, 0 (0, 0, 1, 1) | required属性を指定しているE要素 |
インプット疑似クラス | E:optional |
0, 0, 1, 0 (0, 0, 1, 1) | required属性が指定されていないinput要素 |
インプット疑似クラス | E:valid |
0, 0, 1, 0 (0, 0, 1, 1) | type属性にしたがってバリデーションが正しくおこなわれたinput要素 |
クラスセレクタ | E.foo |
0, 0, 1, 1 (0, 0, 1, 1) | class属性がfooのE要素 |
属性セレクタ | E[attr="value"] |
0, 0, 1, 1 (0, 0, 1, 1) | attr属性の値がvalueであるE要素 |
先ほど示した詳細度のリストを確認するのが分りやすいです。
- 0, 0, 0, 0 - 全称セレクタ、結合子、
:not()
疑似クラス - 0, 0, 0, 1 - 要素セレクタ、疑似要素
- 0, 0, 1, 0 - クラスセレクタ、属性セレクタ
- 0, 1, 0, 0 - IDセレクタ、
:not()
以外の疑似クラス - 1, 0, 0, 0 - style属性(インラインスタイル)
結合子(>
など)は詳細度を上げることがないので無視して計算します。:not()
以外の疑似クラス(:hover
など)はクラスセレクタを1つ追加したというように考えればいいでしょう(例えばa:hover
はa.hover
)。
!important
セレクタは今までの内容を把握しておけば大丈夫だと思いますが、CSSには!important
という規則があります。!important
を指定したプロパティは指定していないプロパティよりも優先して適応されます。
例えば以下の例では#foo
は.foo
よりも詳細度は強いですが、!important
が指定されているプロパティは.foo
が優先されます。
<div id="foo" class="foo"></div>
#foo { display: none; color: red; } .foo { display: block !important; /* 上書きできる */ color: blue; /* 上書きできない */ }
詳細度の計算は!important
の指定されていないプロパティが終わってから、!important
の指定されているプロパティ同士で比較がされます。
まとめ
調べてみてCSS設計のベタープラクティスのメリットを再確認できました。
- クラスセレクタをベースに指定するのはHTMLに依存せずに柔軟に指定ができて、詳細度が弱いセレクタだから。
!important
の使用を控えるのは2回に分けて詳細度を比較する複雑さを避けるため。- 結合子を積極的に利用するのは詳細度を変えずにセレクタの適応範囲を狭くすることができるから。
- style属性を使用しないのは
!important
でしか上書きできないから。 - 詳細度をできるだけ弱く保つと、シンプルに管理をすることができる。
上書き合戦をせずに、期待通りに振る舞うCSSを書くためには仕様を知っておく必要がありますね。