いまだにCSSでつまずくあなたが最初に見つめなおすこと(2)

いまだにCSSでつまずくあなたが最初に見つめなおすこと(2)

いまだにCSSでつまずくあなたが最初に見つめなおすことで自分も同じ過ちを犯しているのですが、よくある、CSSの優先順位の算出方法について。

先日新卒の子にCSSの説明をする機会があり、同僚も同席する中で説明を行いました。
せっかくなので久々に自分の記事を読み返してみたら…

CSSのセレクタには、タイプ別にスコアが定められていて、そのスコアの合計値が大きいものほど優先されます。

HTMLに書かれたstyle = 1000点
idセレクタ = 100点
classセレクタ = 10点
要素名 = 1点

と、書いてありますが・・・

これは間違いとしか言えませんよ!

1年前の自分よ・・・( ゚д゚)

喝を入れてやりたい。
多くの記事でこういう書き方されているし、実際誤解したまま使っている人も多いのですが、自分もこう書いてたとは思わなかった。

ちゃんと説明しなおします!

CSSが効かない原因のひとつ、詳細度の罠

一応、間違いではないのですが…

CSSのセレクタには、タイプ別にスコアが定められていて、そのスコアの合計値が大きいものほど優先されます。

たしかに、合計値なのですが、それは単純にすべてのスコアを足し算したものではありません。

HTMLに書かれたstyle = 1000点
idセレクタ = 100点
classセレクタ = 10点
要素名 = 1点

これは各セレクタの持ち点ではなく、「位」を表しています。

この算出方法は、セレクタの優先順位をつける「詳細度」という概念です。
まあ日本語訳は意味が通じればなんでもいいんですが、仕様上は「specificity」と呼ばれています。
W3CのSelectors仕様の中に「Calculating a selector’s specificity」という項目があります。

count the number of ID selectors in the selector (= a)
count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= b)
count the number of type selectors and pseudo-elements in the selector (= c)
ignore the universal selector

とあるように、a,b,cそれぞれの値を数えます。
で、具体的な例として以下のようなセレクタの詳細度が挙げられています。

*               /* a=0 b=0 c=0 -> specificity =   0 */
LI              /* a=0 b=0 c=1 -> specificity =   1 */
UL LI           /* a=0 b=0 c=2 -> specificity =   2 */
UL OL+LI        /* a=0 b=0 c=3 -> specificity =   3 */
H1 + *[REL=up]  /* a=0 b=1 c=1 -> specificity =  11 */
UL OL LI.red    /* a=0 b=1 c=3 -> specificity =  13 */
LI.red.level    /* a=0 b=2 c=1 -> specificity =  21 */
#x34y           /* a=1 b=0 c=0 -> specificity = 100 */
#s12:not(FOO)   /* a=1 b=0 c=1 -> specificity = 101 */

おや、計算方法合ってるじゃないの?
と思った方・・・

これは罠です。

いいですか、よく読んでください。

Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.

a-b-cの3つの数字を「連結」するとあります。

もしid、class、要素名がそれぞれ1個ずつの場合、パッと見はa=100点、b=10点、c=1点を加算して111点になるように見えますが、本当は1-1-1、なのです。

例として・・・

/* A */
#hoge .moge div {
  color: #f00;
}
/* B */
#hoge div div div div div div div div div div div div {
  color: #000;
}

111点と112点で、Bが勝つ…と思いそうですが、実は、1-1-1と1-0-12で、Aが勝つのです。

証拠はこれです。

See the Pen wWOwPZ by natsumi (@mayo31) on CodePen.18228

アプリのビルド番号や、ブラウザのバージョンとかで1.37.2とかありますよね。
あんな感じです。
マイナーバージョンアップをいくら重ねても、次のメジャーバージョンアップで2.0.0になったら、それが最新となるような感じですね。

CSSとSelectorの仕様をもっと詳しく

ちなみにほかの部分も読んでみると

Selectors inside the negation pseudo-class are counted like any other, but the negation itself does not count as a pseudo-class.

否定疑似クラス(つまり:not()のことですね)については、中身はほかのセレクタと同じようにカウントしてよいが、:not()自体はカウントしないとあります。

こんな感じです。

See the Pen mEobxA by natsumi (@mayo31) on CodePen.

:not()をカウントしないのでスコア的には同じで、後勝ちになっています。

さらに、

the specificity of the styles specified in an HTML style attribute is described in CSS 2.1.

style属性で上書きされたセレクタの持つ特異性についてはCSS2.1で説明してるからね、っと。

ここですね。

count 1 if the declaration is from is a ‘style’ attribute rather than a rule with a selector, 0 otherwise (= a) (In HTML, values of an element’s “style” attribute are style sheet rules. These rules have no selectors, so a=1, b=0, c=0, and d=0.)

style属性の宣言がある場合は、aとして1をカウントし、そうでない場合は0とする。
このCSS2.1の仕様上の書き方だと、一番強いstyle属性をaとして、id=b,class=c,最後の要素名はdとなっています。
ちょっとややこしいですが、詳細度の考え方は同じです。

こういう感じで、ちゃんと仕様を読めば不思議なことなどなくなるのですね。