CSSの詳細度を雑に上書きする

CSSの詳細度

詳細度はCSSをメンテナンス・スケールさせるうえでもっともトラブルを生む要因となりうるものの1つです。詳細度を意識せず、適切にCSSを設計しないと、新しくルールセットを作成しても意図したように変更が反映されない、あるいは意図しないところに変更が反映されてしまう問題が生じます。これらの問題は多くの場合、HTML/CSSの書き直しで修正できますが、その場しのぎの修正はより事態を複雑にしメンテナンスコストを高めます。

この記事ではCSSの詳細度(Specificity)、詳細度問題への事前・事後の対処方法について紹介することでよりよい詳細度ライフを皆さんに送っていただくことを目標とします。

CSSはどう適用されるのか

CSSでは最も詳細度が高く、一番後ろにあるルールセットをもとに、ある要素のプロパティと値が決まります。

詳細度の算出については後述しますがidよりclassが強いことは覚えておきましょう。CSSの詳細度はセレクタを基本としつつ、style属性(style="")、!importantなどいくつかの例外をもとに決定されます。

/*詳細度が同じなので後ろにある.blueからはじまるルールセットが適用される*/
.red{
  color:red
}

.blue{
  color: blue
}

/*詳細度が高い#mainからはじまるルールセットが適用される*/
#main{
  display:none  
}

.main{
  display:list-item
}

CSS中の基本的な詳細度の算出

CSSの詳細度はある要素に対して、

としたときの、a-b-cの値の大きさで決まります。abcの値の合計ではなく、abcそれぞれの値の大きさの比較であることが重要です。aのより大きいセレクタ、aの値が同じ場合はbのより大きい方、bの値が同じ場合はcのより大きい方がより詳細度が高くなります。

/*詳細度の低いものから昇順*/
li                         /* a=0, b=0, c=0 → 0-0-1 */
.class                     /* a=0, b=0, c=0 → 0-1-0 */
.class:hover               /* a=0, b=2, c=0 → 0-2-0 */
#id                        /* a=1, b=0, c=0 → 1-0-0 */
#id.class                  /* a=1, b=1, c=0 → 1-1-0 */
#id.class[data-hoge]       /* a=1, b=2. c=0 → 1-2-0 */
#id_0 > .class + #id_1     /* a=2, b=1, c=0 → 2-1-0 */

擬似クラスとは:hoverなど要素に対して状態をもとにスタイルを適用するためのものです。擬似要素とは:beforeとか:first-lineとかのことです。属性とは属性です。hrefとかです。

例外的な詳細度の算出

CSSの詳細度にはいくつかの例外があります。!importantとstyle属性です。

CSSの詳細度は基本的にはセレクタで決まりますが、!importantにおいてはプロパティと値(宣言)ごとの単位で決まります。また、style属性はCSSやstyle要素中ではなくHTMLの各要素に属性として書き込みます。JavaScriptで要素の属性を書き換えることで、適用されるスタイルを変化させるのはこのためです。

例外的な規則ではありますが、

  • 宣言に!importantがあればx=1
  • style属性(style="")内に書き込まれた宣言であればy=1

として、x-y-a-b-cの値の大きさで詳細度を導き出すことが出来ます。ただし、!importantはセレクタではなく宣言(プロパティと値)ごとに、style属性を使う場合はセレクタは使えないことに留意してください。

/*詳細度が高いのでcolor:redが適用される*/
#id{color:black}      /* x=0, y=0, a=1, b=0, c=0 */
.class{color:red!important} /* x=1, y=0, a=0, b=1, c=0 */

/* !importantがある宣言は詳細度が高くなるのでcolor:redが適用されるが、ない宣言はセレクタごとの詳細度によってfont-size:100remが適用される */
#id{
   color:black;       /* x=0, y=0, a=1, b=0, c=0 */
   font-size: 100rem; /* x=0, y=0, a=1, b=0, c=0 */
}

.class{
  color: red !important; /* x=1, y=0, a=0, b=1, c=0 */
  font-size: 0.2rem;     /* x=0, y=0, a=0, b=1, c=0 */
}
/*style属性が優先される*/
<style>p{color: blue}</style>  <!-- x=0, y=0, a=0, b=0, c=1 -->
<p style="color:red"></p>      <!-- x=0, y=1, a=0, b=0, c=0 -->

詳細度を適切に取り扱うために

CSSの詳細度はここまで述べてきたようになかなかに算出が面倒です。では、この複雑さに対処するためにはどうすればよいのでしょうか。1つはCSS設計などを駆使して複雑さを生じさせないこと、もう1つは詳細度を上書きしていき複雑さに真正面から立ち向かっていくものです。後者は多くの場合ますます事態を深刻にしますが破滅的で楽しいです。

CSS設計

詳細度に悩まされないためには第一にCSSを適切に設計することです。たとえばBEMのようなIDとネストを避け、classオンリーで単一の詳細度のみを扱うCSS Metodologyはその理想形の1つでしょう。ルールセットの並び順だけで適用されるスタイルが決定するため、詳細度を意識する必要はありません。また、SMACSSなどを参考にカテゴリーごとにCSSを整理、IDの適当な使い方を意識することでも問題を軽減できるでしょう。

またCSS WizardryのHacks for dealing with specificityでは

「tl;dr 必要以上に詳しいセレクタを使わない」とまとめています。

事後処理

CSS設計し忘れた、CSS設計が破綻したなどどうしても詳細度の問題で思ったようにスタイルを適用できない場合には、より高い詳細度のセレクタを使って上書きしていくしかありません。今回は詳細度をうまくハックする方法を紹介します。

ザ・ダークサイド・オブ・詳細度

さて、CSS上書きと聞くと!importantの使用を思いつく人が多いのではないでしょうか。しかし!importantは最も事後処理に使うべきではありません。

  • cascadeできない(それ以上重ねられない)
  • 詳細度がセレクタ準拠ではなく宣言準拠になる
  • 打ち消しづらい

などの問題があるからです。

!importantの他に思いつくのはHTMLに適宜手を加え、要素に妥当なidを加えることによる詳細度の調整やCSSの全面的なリファクタリングでしょうか。しかし、いずれもとてもダルいです。ここでは!importantを使わずにCSSをちょこっといじるだけで詳細度に雑に立ち向かえる方法を紹介していきます。

 

!importantを使わないので後からいくらでも雑に上書きできて最高です :-)

idじゃなくて属性セレクタを使う

idセレクタ(#id)の代わりにid属性を活かして属性セレクタを使う方法です。idの詳細度を弱めたい場合には最適ではないでしょうか。idセレクタで簡単に書けるところを少々冗長に書くのでCSSが少し汚くなります。

/* 先述したx-y-a-b-cの算出方法だと0-0-1-0-0で基本的に優先度がclassで上書きできない */
#widget{
  color:red;
}

/* 0-0-0-1-0 */
.current_widget{
  color:blue;
}
/* 属性セレクタを使っているので優先度が0-0-0-1-0とCSSで上書きできる */
[id="widget"]{
  color:red;
}

/* 0-0-0-1-0*/
.current_widget{
  color:blue;
}

セレクタを重ね(連結し)まくる

多くのセレクタは重ねて使うことで、その詳細度を高めます。これを使うとCSSの詳細度を管理するのが非常に難しくなります。一度ハマるとおそらく正常に詳細度を使うことはできなくなるでしょう。

/* 0-0-0-2-0 */
.header .link{
  color:red;
}

/* 0-0-0-5-0 */
.link.link.link.link.link{
  color:blue;
}

/* 0-0-3-1-0 */
#wrapper #main #contents .content{
color:red;
}

/* 0-0-3-1-0 */
#main#main#main .content{
color:blue;
}

:not()を重ね(連結し)まくる

いちばん好きな方法です。:not()、否定擬似クラスはそれ自身に詳細度は存在せず、()内のセレクタの詳細度を適用する点を活かしたものです。詳細度を高めたいセレクタに:not()否定擬似クラスをつけ、中身を適当なidセレクタなどにして、それを重ねておけば完了です。HTMLに特段編集がいらず、idセレクタだろうがなんだろうが無視してえいやっと適当に作れるので重宝します。CSSが最高に汚くなります。

/* 0-0-1-0-1 */
#header li{
  color:red;
}

/* 0-0-1-1-0 */
.link:not(#id){
  color:blue;
}

/* 0-0-3-0-1 */
#wrapper #main #contents article{
font-size:0;
}
/* 0-0-4-0-1 */
article:not(#id):not(#id):not(#id):not(#id){
font-size:100rem;
}

まとめ

先述したような詳細度ハックはもちろん冗談です。CSSの可読性を著しく損なうため通常行うべきではありません。詳細度に事後対処するというのは多くの場合破綻につながるのみです。こんなことにならないように詳細度をきちんと意識して皆さんもいい感じにCSS設計しましょう。

この記事はCSS Architecture Advent Calendarの16日目の記事です。 記事に不正確な点などあれば教えてくださると幸いです。また、この記事は編集途中のものを公開したので至らない点が多くありますが少しずつでも書き直していく所存ですのでアドバイスをいただけると幸いです。

参考URL Hacks for dealing with specificity – CSS WizardryCSS, OOCSS, front-end architecture, performance and more, by Harry Roberts http://csswizardry.com/2014/07/hacks-for-dealing-with-specificity/