Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般
  • 勉強会

【社内勉強会】特濃!CSS講座 #2: セレクタ、カスケード、継承をがっつり理解する

こんにちは、hachi8833です。

babaさんによる「特濃!CSS講座」はここからが佳境です。第2回は皆様が日頃苦しめられている「セレクタ、カスケード、継承」についてです。この部分を仕様からしっかり理解すると、CSSの書きやすさがまるで違ってくるのを感じていただけると思います。

クイズ

突然ですがここでクイズです。

第1問: 以下のCSSとHTMLで表示される文字は何色になるでしょうか?

/* css */
* { color: black; } 
p { color: red; } 
.test { color: blue; } 
p { color: yellow; }
<!-- html -->
<p>
<span class="test">私は何色?</span>
</p>

回答はCodePenをご覧ください。簡単ですね。

第2問: では次の場合は?

/* css */
* { color: red; }
p { color: blue !important; }
<!-- html -->
<p>
<span>私は何色?</span>
</p>

問題の再録と回答はこの記事の後半に記載してあります。

セレクタ、カスケード、継承

講師: baba(CTO、弊社CSS研究部メインパネリスト)

前回(#1)でCSSの最小限の基礎部分を急いでおさらいしました。これで最小限の準備ができましたので、まずはCSSのセレクタの重要なポイントから解説しましょう。

絶対覚えておくべきセレクタ

09

CSSのセレクタは、スタイルを適用する対象を選択するためのものです。CSS2にはさまざまなセレクタがあり、CSS3でも多数追加され、現在も増え続けています。

まず押さえるべきはCSS2セレクタです。すべてをいきなり把握しろとは言いませんが、CSS2セレクタのうち、少なくとも以下に挙げた「シンプルなセレクタ」4つと「セレクタを組み合わせるセレクタ」2つは、何としても理解して覚えてください。「これだけ知っていればいい」ではありませんのでご注意ください。

1: シンプルなセレクタ

選択したいものを指定するだけのシンプルなセレクタです。

セレクタ名 説明
全称セレクタ(Universal Selector) *ですべての要素を選択する *
型セレクタ(Type Selector) タグ名を指定して選択する h1
クラスセレクタ(Class Selector) .クラス名」でクラスを選択する .help
IDセレクタ(ID selector) #ID名」でIDを選択する #main-form
  • 型セレクタは、要素型セレクタとも呼ばれます。
  • 全称セレクタはHTML内のあらゆる要素に対して実際にプロパティを設定します。HTMLヘッダーなど画面に表示されないタグなどについては気にする必要はありません。

↑2項目目はクイズで必要になりますので覚えておきましょう。

2: セレクタを組み合わせるセレクタ

「セレクタを組み合わせるセレクタ」はやや複雑ですが、セレクタの表現がうんと広がります。

セレクタ名 説明
子孫セレクタ(Descendant Selector) 各セレクタをスペースで区切ることで、セレクタ階層を限定して選択できる(paの間に他のタグがあってもよい) p a
属性セレクタ(Attribute Selector) []で属性を指定して選択する [title][title=”BPS”]input[type=text]
  • 子孫セレクタは、セレクタとセレクタの関係を指定するセレクタです。ここでは説明しませんが、子セレクタ、隣接セレクタ、複数のセレクタなども同様です。
  • 属性セレクタでは属性名の他に、属性名の値や、セレクタと属性の関係を指定することもできます。
  • 属性セレクタでセレクタ名や値を省略すると、以下のように「全称」として扱われるので注意が必要です。
    • 属性セレクタでセレクタ名を書かない場合、セレクタの種類にかかわらず指定の対象をすべて選択します。
    • 属性セレクタで値を書かない場合、値にかかわらず指定の対象をすべて選択します。
  • 属性セレクタの属性の値がスペースを含む場合は、値を引用符("や')で囲む必要があります。

カスケードと継承、優先順位

いよいよ本題に近づいてきました。CSS2.1の中核となる「カスケードと継承」「優先順位」について説明します。

10

「カスケード・継承・優先順位については上の図を見よ。以上」で済ませられればよいのですが、CSS初心者や初級者向けに説明が必要ですね。

カスケードとは何か

CSSは「Cascading Style Sheet」の略称ですが、そもそも「カスケード(またはカスケーディング)」って何のことだかおわかりでしょうか?

カスケードとは、「同じ要素に対して異なるスタイルがいくつも指定されたときに、どのスタイルを優先するか」という優先順位付けの処理のことです。つまり「どのスタイル指定を勝たせるのか」です。

カスケードを理解するために、origin、詳細度、継承について少し説明します。

CSSのorigin

まずはoriginから。

10_origin

ここで重要なのは、CSSを設定できるのはWebサーバーの開発者だけではないということです。独学でCSSを多少いじった程度だと気づかない方もいるかもしれませんが、CSSを設定できるのは以下の3つの立場です。これらの立場がCSSの仕様でorigin(出自)と呼ばれています。

  1. HTML文書作成者(author)
  2. ユーザー(user)
  3. ブラウザなどのUA(user agent)

以下、originについて簡単に説明します。

1: HTML文書作成者

Webページの作成者であり、送り手側です。通常のWeb開発やページ制作でCSSを書く場合、ほぼ間違いなくこのHTML文書作成者の立場で書くことになります。

2: UA(ブラウザなど)

UAはUser Agentの略ですが、普通はWebブラウザを指します。人間が直接見ることのないHTMLレンダリングエンジンなどもWebブラウザと振る舞いが同じなので、それらを総称したものがUAと呼ばれています。

つまり、Webブラウザ自身もデフォルトのCSSを持っているのです。Webブラウザ自身にCSSがまったくなかったら、CSSをまったく使っていないHTMLを表示したときに何のスタイルもないベタ書きの表示になってしまうので、最小限のCSSが用意されているのです。

161003_1115_pCySMv

そしてこのデフォルトCSSは、Webブラウザの種類やバージョンによって少しずつ異なっています。この違いは普通は問題になりませんが、CSSによるデザインやレイアウトを深く追求すると問題になってくることもあります。

上のデフォルトCSSは最新とは限りませんのでご了承ください。

3: ユーザー

ユーザーというのはWebブラウザのユーザーのことですが、言うなればユーザーがブラウザの表示を自分好みにカスタマイズするときに使うCSSを指します。ブラウザの設定でフォントの種類やサイズを変更すると、このユーザー用CSSが変更されていることになります。

一般ユーザーにはそれほど知られていませんが、ユーザーがブラウザの表示で変えられるのはフォントの種類や大小だけではありません。たとえばChromeにStylysh という拡張機能をインストールすると、ユーザーが特定のページのCSSをいくらでも自分好みに変更できます。これもユーザーCSSに該当します。

161003_1112_W8XVCQ

ユーザーがCSSを自由に変更できる設定画面がブラウザに用意されていない理由は、ユーザーがCSSをいじりすぎて壊してしまうことのないように隠してあるというだけのことです。

originの優先順位

先ほどリストしたoriginに番号が表示されているのでおわかりかと思いますが、originは次の順序で優先されます。

  1. HTML文書作成者
  2. ユーザー
  3. ブラウザなどのUA

つまりブラウザのデフォルトCSSよりユーザー定義のCSSの方が勝ち、HTML文書作成者はさらにそれより強くなります。

作り手の意図を優先するのはしごくもっともな考えです。そして作り手が指定していない部分についてはユーザーCSSでカスタマイズでき、何も指定されていない部分には安全ネットとしてブラウザのデフォルトCSSが使われる、という設計も自然ですね。

!important指定

さて、皆様はきっと!importantという魔法のようなキーワードをご存知かと思います。ひとたびこれを知ってしまうと、CSSがなかなか思うようになってくれないときについふらふらと使いたくなってしまいますが、!importantは果たして万能なのでしょうか。

!importantを指定すると、上のoriginの優先順位が以下のように変更されます。

  1. ユーザー(!importantを指定)
  2. HTML文書作成者(!importantを指定)
  3. HTML文書作成者
  4. ユーザー
  5. ブラウザなどのUA

!importantなしの場合の優先順位と並べてみると、上に1.と2.が追加されているのがわかります。

3〜5の順位は先ほどと同じです。!importantを指定した1.と2.では、HTML文書作成者のCSSではなくユーザーのCSSが勝っていることにご注目ください。3.と4.の順位を上げて1.と2.にしただけではなく、さらに入れ替わっています

これにより、!importantが指定されたプロパティはユーザーのスタイルシートの方が優先されるようになり、HTML文書作成者のスタイルシートで同じプロパティに!importantを指定しても勝てません。

/* ユーザーのスタイルシート: */
p { text-indent: 1em !important } // こっちが勝つ

/* HTML文書作成者のスタイルシート */
p { text-indent: 1.5em !important } // こっちはユーザーのスタイルシートに絶対勝てない

Stylyshのような拡張機能では間違いなく!important指定が内部で使われていますが、それによってWebサーバーのCSSを確実に打ち消すことができます。逆にWebサーバーのCSSで!importantを付けても、こうした「ユーザー+!important」には勝てません。

!importantの理解にはoriginの理解が必要

先ほど、CSSの優先順位のトップにあった「カスケードで指定されたもの」の中で、さらに以下のように「Originの優先順」が優先されると説明いたしました。

10_origin2

皆様大好きな!importantは、このoriginの優先順位に密接に関連していることがおわかりいただけたかと思います。!importantは決して万能ではなく、指定したからといって必ず効くものではありません。!importantを指定されたプロパティ同士の勝負は、後述の「セレクタの詳細度」などの他の優先順位で決まります。

そうした点を理解せずに!importantを乱用すると、後でいろいろ困ったことになります。originをしっかり理解して、!importantはここぞというときにだけ使うようにしましょう。

スタイルを思うように設定できないよくある原因のひとつとして、jQueryなどの外部JavaScriptライブラリが自分の知らないところで!importantを使っていることがあります。うまくいかないからといって!importantで切り抜けようとする前に、こうした外部ライブラリで!importantが使われていないか疑ってみましょう。

参考: !importantに関連する仕様

セレクタの詳細度

originに続いてセレクタの詳細度(specificity)を解説します。

161003_1627_ILGX8f

詳しくは他のサイトに譲りますが、詳細度については「具体的なセレクタ」「対象を絞り込んだセレクタ」であるほど優先順位が高くなり、「大ざっぱなセレクタ」であるほど優先順位が下がります。

セレクタ名 説明
全称セレクタ * 0点(大ざっぱなので低い)
型セレクタ、疑似要素 liなど 1点(少し高い)
クラスセレクタ、擬似クラス、属性セレクタ li:first-classなど 10点(さらに少し高い)
IDセレクタ #idなど 100点(さらに高い)
HTMLタグのstyle属性に直接書き込む場合(※インラインCSS: これだけ要素を指していない) <div style="color: red">など 1000点(めちゃ高い)

上の点数を組み合わせたものが点数になり、高いほどセレクタの詳細度内での優先順位が上がります。Specificity Calculator などの詳細度算出サービスでチェックすることもできます。

161003_1207_s5eOv9

点数が0、1、10、100、1000と配分されているのは、たとえばクラスセレクタが5、6個指定されているぐらいでIDセレクタが負けることのないようにするためです。クラスセレクタが10個あればIDセレクタに勝てることになりますが、そんなおかしなCSSを書くことはまずないので問題になりません。

面白いのは、要素でも属性でもない「インラインCSS」(style属性)がこの優先順位に含まれていることでしょう。インラインCSSはセレクタの詳細度の中では1000点と最強ですが、セレクタの詳細度自体はoriginよりも優先順位が低いので、!importantで勝てるわけです。

参考: セレクタの詳細度に関連する仕様

いずれもCSS2.1です。

後に記述したもの

161003_1638_e42Wz9

origin、セレクタの詳細度(specificity)に続いて後に記述したものについて説明します。これは割とシンプルです。

CSSは、ブラウザで読み込まれた順に処理されます。同じ優先順位のスタイルが複数読み込まれた場合、他の条件が同じであれば、後出しジャンケンのごとく後から読み込まれたスタイルが勝ちます

以下のようにoriginとセレクタの詳細度が同じであれば、後から読み込まれたプロパティが使われます。

.myclass {
  color: yellow;
  color: black; #<= これが勝つ
}

複数のCSSファイルをプリプロセッサで1つにまとめることがよく行われますが、このときの順序が表示に影響する可能性があります。

なお、htmlのheadタグ内に直接CSSを書く、style要素(以下の6行目)は最後に読み込まれます。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>タイトル</title>
  <style type="text/css">p { color:yellow;}</style>
</head>
<body>
  何か書く
</body>
</html></code>

以上で、「1. カスケードで指定されたもの」の3つの優先順位「origin」「セレクタの詳細度」「後に記述したもの」について説明しました。

161003_1533_yQxQ3G

残るは「2. 継承されたもの」と「3. 初期値」です。

CSSの継承

CSSの継承(inheritance)について説明します。

161003_1640_1lBOhu

CSSの継承とは、HTMLタグの階層構造(文書ツリー)における親から子にCSS設定(プロパティ)が引き継がれることです。親要素のプロパティは子要素に引き継がれるというシンプルな原則です。

たとえば、<body></body>要素にスタイルを設定すると、そのスタイルのプロパティが<body></body>要素の下にあるすべての子要素やその属性にも引き継がれます。すぐ下の子だけではなく、その下の子々孫々にも引き継がれます。

161003_1259_crKFdG

「継承」では、親要素や先祖要素のプロパティを(コピーせずに)単に参照します。この後クイズを出しますので、ここをよく覚えておいてくださいね。

継承されるのは「CSSプロパティ」

ここでご注意いただきたいのは、継承は文書ツリーの要素<body>divなど)の親子関係に沿って行われますが、継承する/されるものはCSSのプロパティcolorfont-weight)であるということです。

繰り返しになりますが、CSSのプロパティは要素の親子関係に沿って継承されます。言い方を変えれば、継承の道筋と、継承されるものは同じではないのです。これもクイズを解くために必要ですのでよく覚えておいてください。

プロパティには、継承可能なプロパティと継承できないプロパティがあります。プロパティが継承可能かどうかは、CSS2.1の全プロパティ一覧で確認できます。「inherited?」列がyesのプロパティは、子孫要素に継承されます。

161003_1312_crmhEd

要素に既にあるプロパティについては継承しない

親要素や先祖要素から継承されるのは、カスケード処理で要素に設定されなかったプロパティです。

main { color: blue;}
article {} /* 子にcolorプロパティがなければ、親のcolor: blueを継承する*/

要素に既にあるプロパティは、親や先祖に同じプロパティがあっても継承されませんし、既にあるプロパティを継承だけで上書きすることはどうやってもできません。継承はカスケードより弱いのです。

以下の例では親要素がmainタグ、子要素がarticleタグになりますが、この場合articleのredが継承によって打ち消されてblueになるようなことはありません。

main { color: blue;}  /* 親要素への指定*/
article {color: red;} /* 子要素への指定: このプロパティが継承で変更されることはない*/

この動作は、要素に他のプロパティがあろうとなかろうと同じです。

main { color: blue;}
article {
  color: red; /* 下のfontプロパティがあろうとなかろうと、このプロパティが継承で変更されることはない*/
  font: italic normal bold 80%/150% "MS Pゴシック";
}

継承しない場合

親子関係ですべてのプロパティが継承されてしまうと、実用上不便です。たとえば親のボックスに設定した色がすべての子孫に設定されてしまうと、ボックスの色が親子で全部同じになってしまって邪魔です。

このため、背景やボックス関連のプロパティなどは子孫に継承されないようになっています。ざっくりですが、テキストスタイル関連のプロパティは継承するものが多く、色や境界線関係のプロパティは普通継承しません。

継承の有無はプロパティごとに定められているので、よく確認しましょう。

161003_1656_CUiGyt

なお、親や先祖のプロパティを継承するかどうかはCSSで臨時に変更することもできます。

初期値

最後の「初期値」は、ブラウザのデフォルトのCSSです。他で設定されなかったプロパティについては、ここで安全ネット的にスタイルが指定されます。

161003_1853_32ypeg

最終的な優先順位

以上で優先順位の説明は終わりました。それでは改めて優先順位を眺めてみましょう。

注意: 「メディアタイプ」が一致していないスタイルは優先順位にかかわらず除外されるので、以下の優先順位には含めていません。

  1. カスケードで指定されたもの
    1. originの優先順(!importantの指定もここ)
    2. セレクタの詳細度が高いものstyle=""タグもここ)
    3. 後から読み込まれたもの<style>要素もここ)
  2. 文書ツリー上の親や先祖から継承されたもの
  3. ブラウザの初期値CSS(UA)

この優先順位を元に、CSSの実際の挙動を理解してみましょう。

優先順位からCSSの挙動を理解する

  • クラスセレクタは、後から読み込まれた型セレクタに勝てます。.testは、その下のpに勝っていますのでテキストは青になります。(CodePen

160828_1128_FEmonj

  • ブラウザの初期値CSS(UA)は常に最弱です。しかしたまたま他の誰も設定していないスタイルがあれば、安全ネット的にスタイルを設定してくれます(このような動作を一般に「フォールバック」と言います)。

  • HTMLヘッダの<style>要素は他のCSSより後から読み込まれるので、CSSファイルのスタイルに勝ち、文字は赤色になります(CodePen)。

160828_1148_ZGp1uF

  • しかしCSSファイルの詳細度が高ければ、後から読み込まれた<style>要素といえども勝てません(CodePen)。詳細度は、型セレクタpよりもクラスセレクタ.testの方が上です。したがって<style>要素は負け、.testクラスで指定した青色になります。

160828_1155_rDglbk

  • HTMLタグに直接記入するインラインstyleタグのクラスセレクタは、CSSファイルのクラスセレクタはもちろん、HTMLヘッダのstyle要素のクラスセレクタにも勝っています(CodePen)。したがって文字は赤色になります。

160828_1210_2ZhcvY

CSSファイルとHTMLヘッダのstyle要素で、クラスをIDに変えても、やはりstyleタグが勝ちます(CodePen)。したがって、文字は赤色のままです。

161004_1135_nAL1Zj

style属性にはセレクタはありませんが、style属性を書くこと自体がセレクタになっているので、これもセレクタの一種です。

style属性はstyle要素より後から読み込まれるから強い」という説明をたまにみかけますが、そうではなく、セレクタ詳細度が最も高い(1000点)から強いということがこれでわかります。

style属性はHTML要素に最も近いところにあるので、直感的にも最強であると考えられます。この直感を実現するために、仕様でstyle属性に1000点という高いセレクタ詳細度が設定されていると考えることもできます。

  • HTML文書作成者が!importantを使うことで、この強力なstyle属性に勝てます(CodePen)。弱々しい型セレクタpにもかかわらず、!importantを追加したことで<style>要素のIDセレクタやHTMLヘッダのstyleタグよりも優先されている点がポイントです。したがって、文字は青色になります。

161003_1922_NWCqu6

このことからも、importantはセレクタ詳細度より上の階層で優先度が設定されていることがわかります。

  • しかし!importantの項で説明したように、HTML文書作成者が!importantを使ってもユーザーがブラウザ側で設定した!importantスタイルには勝てません。この動作はCodePenでは表しきれないので省略します。

追伸

CSSをインラインstyleとしてHTMLに直接注入する邪魔なjQueryプラグインがたまにありますが、そうしたプラグインに対抗するためにHTML文書作成者として!importantを使わざるを得ないこともあります。

CSSカスケードクイズ(再録)

ではここで冒頭のクイズを繰り返します。以下のCSSとHTMLで表示される文字は何色になるでしょうか?ここまでみっちり読み進めた方にはもう楽勝だと思います。

第1問: 以下のCSSとHTMLで表示される文字は何色になるでしょうか?

/* css */
* { color: black; } 
p { color: red; } 
.test { color: blue; } 
p { color: yellow; }
<!-- html -->
<p>
<span class="test">私は何色?</span>
</p>

回答はCodePenをご覧ください。

第2問: では次の場合は?

/* css */
* { color: red; }
p { color: blue !important; }
<!-- html -->
<p>
<span>私は何色?</span>
</p>

解答は次の節にありますが、ぜひ一度考えてからご覧ください。

第2問の解答

CodePen を見てびっくりしていただけましたでしょうか。型セレクタpと最強!importantとのコンビが、弱々しい全称セレクタ*に負けてしまうなんて、これまでの説明ではなかなかすっと理解できない動作ですね。

結論から言うと、全称セレクタは継承のように見えて継承とは違うのです

別のコードでもう少し詳しく見てみましょう(CodePen)。

* { color : red ; }
body { color: yellow;}
p {color: blue;}
<div>
<p><strong>pタグとstrongタグ</strong>の場合</p>
<p><span>pタグとspanタグ</span>の場合</p>
</div>
タグの外

161003_1834_dRk1Vo

先ほどの最終的な優先順位を思い出してみてください。

  1. カスケードで指定されたもの
    1. originの優先順(!importantの指定もここ)
    2. セレクタの詳細度が高いものstyle=""タグもここ)
    3. 後から読み込まれたもの<style>要素もここ)
  2. 文書ツリー上の親や先祖から継承されたもの
  3. ブラウザの初期値CSS(UA)
  • 全称セレクタ*は確かに詳細度(1-2)の中では最弱ですが、それでも継承したスタイル(2)よりは優先順位が高くなっています。
  • 全称セレクタによる大ざっぱな色指定* { color : red ; }は、body { color: yellow;}での大ざっぱな指定と違いはないように見えますが、違います
    • * { color : red ; }は、あらゆる要素1つ1つに実際にそのスタイルを適用するのと同等です。スタイルの継承ではなく、スタイルをすべての要素に律儀につけて回っているご苦労さんなセレクタです。つまり、全称セレクタ*を使うと継承をぶっ飛ばしてしまうのです
    • body { color: yellow;}では、実際にスタイルが適用されているのは<body>だけです。そして文書ツリーで<body>より下にあるすべての子孫要素は、<body>のスタイルを参照しているだけです(これが継承です)。

    • 参考: 図の「タグの外」という文字はタグに囲まれていないので、唯一全称セレクタ*の影響を受けておらず、文字がbodyで指定した黄色になっています。

問題2のコードに戻って説明すると次のようになります。

160828_1452_gAj19s

  • 全称セレクタ*をうかつに使ったために、一見無指定のように見える<strong>要素や<span>要素に意図せずしてcolor: redプロパティが設定されてしまった。
  • その結果<strong>要素や<span>要素はcolor: redプロパティを持ってしまったので、親要素にある同じcolorプロパティを継承しなくなってしまった。

このように、全称セレクタをうかつに使うとそのプロパティの継承関係がグローバルに妨害されることがあるので注意しましょう。無条件の全称セレクタを現場で実際に使う必要性は、ほぼないはずです。全称セレクタは、どうしても必要な場合のみ子孫セレクタなどで対象を絞り込んだうえで使うようにしましょう。

追伸

ブラウザの実装次第ですが、全称セレクタを使うと遅くなると言われるのもあながち根拠がないわけではなさそうです。

まとめ

  • CSSのプロパティは、カスケードと継承が組み合わさった優先順位に従って適用される
  • 他の条件が同じなら、優先順位の高いプロパティが適用される
  • 継承はカスケードより弱い
  • 継承で既存のプロパティを上書きすることはできない
  • 全称セレクタ*は継承をぶっとばす
  • !important指定は最強とは限らない
  • HTMLタグのstyle属性も最強とは限らない
  • ユーザーがブラウザでカスタム指定した!importantには勝てない

CSS2.1のセレクタ・カスケード・継承をここまで理解できれば、あなたのCSS力は確実に2ランクアップするでしょう。

そして第一回講義はここまででやっと実時間で20分経過したところです。残りは40分。
次回は「ボックスモデルとレイアウト」で、ボックスレイアウトとインラインレイアウトの肝を解説します。どうぞお楽しみに。

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。