競合の処理
このレッスンの目的は、CSS の最も基本的な概念であるカスケード、詳細度、継承について理解を深めることです。これらの概念は、CSS を HTML に適用する方法とスタイル宣言の間の競合を解決する方法を制御するものです。
このレッスンに取り組むことは、すぐに役立つものではなく、このコースの他の部分よりも理論的であるように思えるかもしれませんが、これらの概念を理解することは、後々、多くの苦労を回避することにつながります。この章を慎重に学習し、概念を理解したことを確認してから、次の章に移ることをお勧めします。
前提知識: | HTML の基本( 基本的な HTML の構文を学んでいること)、基本的な CSS セレクター。 |
---|---|
学習成果: |
|
競合するルール
CSS は Cascading Style Sheets の略で、最初の単語であるカスケード (cascading) を理解することは非常に重要です。カスケードのふるまいは、CSS を理解するための鍵となりえます。
プロジェクトに取り組んでいるとき、要素に適用されているはずの CSS が機能していないと感じることがあります。大抵の場合、この問題は同じ要素に適用される可能性のある 2 つ のルールを作ってしまったことが原因です。カスケードおよび、それと密接に関連する詳細度の概念は、そのような競合が存在する際にどちらのルールを適用するかを制御するメカニズムです。実際に要素にスタイルを設定しているルールがどれなのかは期待と異なる場合があるため、このような仕組みがどのように動作するのかを理解しておく必要があります。
このほかに重要なのは継承という概念です。CSS プロパティによって、親要素の値を既定で継承するものもあれば、継承しないものもあります。これにより、予期しない動作が発生する可能性もあります。
まず重要なものを簡単に見てみましょう。それぞれ順に追っていって CSS でどのように相互作用するかを見ていきます。これらはトリッキーな概念のように見えるかもしれませんが、CSS を書く練習を積んでいくと、その動作が明らかになってくるでしょう。
カスケード
スタイルシートのカスケードは、とてもシンプルに考えるなら、これは発生元と、カスケードレイヤーと、CSS ルールの順序によるということです。同じカスケードレイヤーからの 2 つのルールが適用されており、どちらも詳細度が同じである場合、スタイルシートで最後に定義されたものが使用されます。
下記の例では、<h1>
要素に適用できる 2 つのルールがあります。<h1>
のコンテンツは結果的に青く色づけされています。これは、どちらのルールも同じソースにあり、同じ要素セレクターを持ち、したがって詳細度が同じですが、ソース上の順番が最後のものが勝者となるからです。
<h1>これは見出し</h1>
h1 {
color: red;
}
h1 {
color: blue;
}
詳細度
詳細度とは、ある要素にどのプロパティの値を使用するかを決めるために、ブラウザーが使用するアルゴリズムです。複数のスタイルブロックに、同じプロパティを異なる値で設定する異なるセレクターがあり、同じ要素を対象としている場合、その要素に適用されるプロパティの値は、詳細度によって決定されます。詳細度とは、基本的にセレクターの選択がどの程度具体的であるかを示す指標です。
- 要素セレクターは詳細度が低く、ページ上に現れるその種類のすべての要素を選択するので、あまり重みがありません。擬似要素セレクターは、通常の要素セレクターと同じ詳細度を持ちます。
- クラスセレクターはより詳細度が高く、ページ上にある特定の
class
属性値を保有する要素のみを選択するので、より高い重みを持っています。属性セレクターと擬似クラスは、クラスと同じ重みを持ちます。
下記の例では、再び <h1>
要素に適用できる 2 つのルールがあります。下記の <h1>
のコンテンツは、クラスセレクター main-heading
によりそのルールが高い詳細度を持つため、結果として赤で表示されています。たとえ、<h1>
要素のセレクターを使用したルールがソースの順序でより下に現れても、クラスセレクターを使用して定義された、より高い詳細度を持つルールが使用されることになります。
<h1 class="main-heading">これは見出し</h1>
.main-heading {
color: red;
}
h1 {
color: blue;
}
詳細度のアルゴリズムについては、後ほど説明します。
継承
ここでは継承についても理解する必要があります。親要素に設定された CSS プロパティ値には、子要素に継承されるものとそうでないものがあります。
例えば、ある要素に color
と font-family
を設定すると、異なる色やフォントの値を直接適用しない限り、その中にあるすべての要素もその色やフォントでスタイル設定されます。
<p>
body が青の色が設定されているため、これは子孫にも継承されます。
</p>
<p>
セレクターで要素を対象とすることで、色を変更することができます。例えば、このような <span>span</span> です。
</p>
body {
color: blue;
}
span {
color: black;
}
いくつかのプロパティは継承されません。例えば、要素に width
50% と設定した場合、すべての子孫要素は親の幅の 50% の幅を取得しません。もしそんなことになるのら CSS を使うととてもイライラするでしょう。
メモ: MDN の CSS プロパティリファレンスページには、「公式定義」という技術情報ボックスがあり、そのプロパティに関するいくつかのデータポイント(継承されるかどうかなど)が記載されています。例として、color プロパティの公式定義の節を参照してください。
これらの概念がどう連携するか理解する
これら 3 つの概念(カスケード、詳細度、継承)は、どの CSS がどの要素に適用されるかを制御するものです。以下の節では、これらがどのように連携して動作するのかを見ていきます。少し複雑に感じることもあるかもしれませんが、CSS を経験していくにつれて覚え始めるでしょうし、忘れたときには常に詳細を調べておくことができます。経験豊富な開発者でも、すべての詳細を覚えているわけではありません。
継承を理解する
継承から始めましょう。次の例では <ul>
要素があり、内部にさらに 2 つ のレベルの順序なしリストがネストされています。外側の <ul>
に境界線、パディング、文字色が指定されています。
color
プロパティは継承されるプロパティです。よって、color
プロパティの値は直接の子だけでなく、間接的な子にも適用されています。つまり直接の子である <li>
、それに最初のネストされたリスト内のものにも適用されています。そして 2 番目 にネストされたリストに special
クラスを追加し、別の色が適用されています。これは子に継承します。
<ul class="main">
<li>アイテム 1</li>
<li>
アイテム 2
<ul>
<li>2.1</li>
<li>2.2</li>
</ul>
</li>
<li>
アイテム 3
<ul class="special">
<li>
3.1
<ul>
<li>3.1.1</li>
<li>3.1.2</li>
</ul>
</li>
<li>3.2</li>
</ul>
</li>
</ul>
.main {
color: rebeccapurple;
border: 2px solid #ccc;
padding: 1em;
}
.special {
color: black;
font-weight: bold;
}
(前述のように)width
や margin
、padding
、border
といったプロパティは継承されるプロパティではありません。もし、このリストの例で境界線が子プロパティに掲載されていたら、すべてのリストとリスト項目に境界線が追加されることになります。
CSS のプロパティのページには、そのプロパティが継承されるかどうかが掲載されていますが、プロパティの値がどのようなスタイルを設定するのかを知っていれば、直観的に同じことを推測できることが多いでしょう。
継承の制御
CSS は、継承を制御するための 5 つの特別なユニバーサルプロパティ値 (universal property values) を提供します。すべての CSS プロパティはこれらの値を受け入れます。
inherit
-
選択した要素に適用されるプロパティ値を、その親要素と同じものに設定します。これは「継承を有効にする」ことを意味します。
initial
-
選択した要素に適用されるプロパティ値を、そのプロパティの初期値に設定します。
revert
-
選択した要素に適用されるプロパティ値を、そのプロパティに適用されている既定値ではなく、ブラウザーの既定スタイル設定にリセットします。この値は、多くの場合
unset
のように動作します。 revert-layer
-
選択した要素に適用されるプロパティ値を、前回のカスケードレイヤーで設定された値にリセットします。
unset
-
プロパティを自然な値にリセットします。つまり、プロパティが自然に継承される場合は
inherit
のように動作し、そうでない場合はinitial
のように動作します。
メモ: それぞれについて、またこれらがどのように動作するのかについては、オリジンの種類を参照してください。
リンクのリストを見ると、ユニバーサル値 (universal values) がどのように機能するかを調べることができます。以下のライブサンプルでは、CSS に変更を加えて何が起こるかを確認できます。HTML と CSS を理解するには、実際にコードを試すのが最善の方法です。
例えば次のようになります。
- 2 番目 のリストアイテムには、
my-class-1
が適用されています。これは、内部にネストされた<a>
要素に色を継承 (inherit
) します。ルールを削除すると、リンクの色はどのように変わるでしょうか? - 3 つ目と 4 つ目のリンクがなぜそのような色になっているのか、お分かりでしょうか? 3 つ目のリンクは
initial
に設定されています。これはプロパティの初期値(この場合は黒)を使用し、ブラウザーのリンクの既定値である青は使用しない、という意味です。4 番目のリンクはunset
に設定されており、これはリンクテキストが親要素の色である緑を使用することを意味しています。 <a>
要素に新しい色を定義した場合、どのリンクの色が変わるでしょうか。例えば、a { color: red; }
のようにした場合です。- 次の節の「すべてのプロパティ値のリセット」を読んだ後、また戻ってきて
color
プロパティをall
に変更してみてください。2 つ目のリンクが新しい行になり、箇条書きがあることに注目してください。どのようなプロパティが継承されていると思いますか。
<ul>
<li>既定の<a href="#">リンク</a>色</li>
<li class="my-class-1">継承した<a href="#">リンク</a>色</li>
<li class="my-class-2">リセットした<a href="#">リンク</a>色</li>
<li class="my-class-3">設定解除した<a href="#">リンク</a>色</li>
</ul>
body {
color: green;
}
.my-class-1 a {
color: inherit;
}
.my-class-2 a {
color: initial;
}
.my-class-3 a {
color: unset;
}
すべてのプロパティ値のリセット
CSS の一括指定プロパティ all
を使用して、これらの継承値の 1 つ を(ほぼ)すべてのプロパティに一度に適用できます。その値として、いずれかの継承値(inherit
、initial
、revert
、revert-layer
、unset
のいずれか)を指定することができます。新しく変更を開始する際、既知の開始点に戻ることができるように、変更されたスタイルを元に戻す便利な方法です。
以下の例では 2 つ のブロック引用 (blockquote) 要素があります。最初のスタイルは blockquote 要素自体に適用されます。2 つ目には all
に unset
を設定するように blockquote に適用されるクラスがあります。
<blockquote>
<p>この引用ブロックはスタイル設定されています。</p>
</blockquote>
<blockquote class="fix-this">
<p>この引用ブロックはスタイル設定されていません。</p>
</blockquote>
blockquote {
background-color: orange;
border: 2px solid blue;
}
.fix-this {
all: unset;
}
all
の値を他の有効な値に設定してみて、違いを観察してみてください。
カスケードを理解する
これで、HTML の構造の奥深くにある段落が、本体に適用されている CSS と同じ色になるのは、継承のためだということが理解できたと思います。入門編で、文書内の任意の場所で何かに適用される CSS を変更する方法について理解しました。 CSS を要素に割り当てるか、クラスを作成するかです。これで、複数のスタイルブロックが同じ要素に異なる形で同じプロパティを適用する場合、どの CSS ルールが適用されるかをカスケードで定義する方法を見て取ることができます。
考慮すべき 3 つ の要因がありますが、ここでは重要度の高い順にリストしています。前にあるものは、後のものを無効にします。
- ソース順 (Source order)
- 詳細度 (Specificity)
- 重要度 (Importance)
これらを下から順に、ブラウザーがどうやって CSS を適用しているのかを見ていきましょう。
ソース順
ソース順がカスケードにとって重要であることは、既に見たとおりです。複数のルールを保有し、それらのルールがすべてまったく同じ重みを持っている場合、CSS の最後に来るルールが優先されます。これは、要素自体に近いルールが、最後のルールが勝利して要素のスタイルを取得するまで、前のルールを上書きすると考えることができます。
ソース順が問題になるのは、ルールの詳細度に対する重みが同じである場合だけなので、詳細度について考えてみましょう。
詳細度
あるルールがスタイルシートの後半に来ることは分かっていても、より前の、競合するルールが適用される状況がよくあります。これは、前のルールが より高い詳細度 を有しているため、つまり、より詳細であるため、ブラウザーが要素をスタイル設定すべきものとして選択した場合に起こります。
このレッスンで以前に見たように、クラスセレクターは要素セレクターよりも重みがあるため、クラスのスタイルブロックで定義されたプロパティは、要素のスタイルブロックで定義されたものを上書きすることになります。
ここで注意したいのは、セレクターとそれが選択するテキストや成分に適用されるルールに注目していますが、上書きされるのはルール全体ではなく、複数の場所で宣言されているプロパティだけだということです。
この動作は、CSS の繰り返しを避けるのに役立ちます。一般的な方法としては、基本的な要素には一般的なスタイルを定義し、異なる形の要素にはクラスを作成することです。例えば、下記のスタイルシートでは、レベル 2 の見出しに対して一般的なスタイルを定義し、その後、一部のプロパティと値のみを変更するクラスをいくつか作成しています。最初に定義された値はすべての見出しに適用され、その後、より詳細度の高い値がクラスを持つ見出しに適用されます。
<h2>クラスのない見出し</h2>
<h2 class="small">small クラスのついた見出し</h2>
<h2 class="bright">bright クラスのついた見出し</h2>
h2 {
font-size: 2em;
color: #000;
font-family: Georgia, "Times New Roman", Times, serif;
}
.small {
font-size: 1em;
}
.bright {
color: rebeccapurple;
}
ブラウザーが詳細度を計算する方法を見てみましょう。要素セレクターの詳細度は低く、クラスで上書きできることはすでにわかったはずです。基本的に、セレクターの重みはポイント単位の値で与えられ、これらを合計して特定セレクターの重みが与えられて、他の一致するものと相対して評価することができます。
セレクターが持つ詳細度の量は、3 つ の異なる値(またはコンポーネント)を使用して測定されます。これは、ID、クラス、要素の列を、それぞれ百の位、十の位、一の位として考えることができます。
- ID: この列は、全体のセレクターの中に含まれるそれぞれの ID セレクターに対して 1 点ずつ評価します。
- クラス: この列は、クラスセレクター、属性セレクター、擬似クラスが全体のセレクターの中に含まれている場合に 1 点ずつ評価します。
- 要素: この列は、要素セレクターまたは擬似要素が全体のセレクターの中に含まれている場合に 1 点ずつ評価します。
否定 (:not()
)、関係セレクター (:has()
)、match-any (:is()
) 擬似クラス、 CSS 入れ子自身は、詳細度に影響を与えませんが、それらの引数や入れ子ルールには影響を及ぼします。それぞれが詳細度重みアルゴリズムに寄与する詳細度の重みは、最も大きな重みを持っている引数または入れ子ルールのセレクターの詳細度です。
次の表でわかりやすいいくつかの例を示します。これらを試してみて、なぜ詳細度が与えられるのかをしっかり理解してください。セレクターについてはまだ詳しく説明していませんが、MDN の セレクターリファレンスで詳細を参照することができます。
セレクター | ID | クラス | 要素 | 詳細度の合計 |
---|---|---|---|---|
h1 |
0 | 0 | 1 | 0-0-1 |
h1 + p::first-letter |
0 | 0 | 3 | 0-0-3 |
li > a[href*="en-US"] > .inline-warning |
0 | 2 | 2 | 0-2-2 |
#identifier |
1 | 0 | 0 | 1-0-0 |
button:not(#mainBtn, .cta ) |
1 | 0 | 1 | 1-0-1 |
先に進む前に、実例を見てみましょう。
<div class="container" id="outer">
<div class="container" id="inner">
<ul>
<li class="nav"><a href="#">One</a></li>
<li class="nav"><a href="#">Two</a></li>
</ul>
</div>
</div>
/* 1. 詳細度: 1-0-1 */
#outer a {
background-color: red;
}
/* 2. 詳細度: 2-0-1 */
#outer #inner a {
background-color: blue;
}
/* 3. 詳細度: 1-0-4 */
#outer div ul li a {
color: yellow;
}
/* 4. 詳細度: 1-1-3 */
#outer div ul .nav a {
color: white;
}
/* 5. 詳細度: 0-2-4 */
div div li:nth-child(2) a:hover {
border: 10px solid black;
}
/* 6. 詳細度: 0-2-3 */
div li:nth-child(2) a:hover {
border: 10px dashed black;
}
/* 7. 詳細度: 0-3-3 */
div div .nav:nth-child(2) a:hover {
border: 10px double black;
}
a {
display: inline-block;
line-height: 40px;
font-size: 20px;
text-decoration: none;
text-align: center;
width: 200px;
margin-bottom: 10px;
}
ul {
padding: 0;
}
li {
list-style-type: none;
}
何が起こっているのでしょうか?まず、この例の最初の 7 つ のルールにのみ関心があり、お気づきのように、各ルールの前に詳細度の値をコメントしてあります。
- 最初の 2 つ のセレクターはリンクの背景色について競合しています。2 番目 のセレクターには ID セレクター があるのでそれが優先され、青になります。この詳細度は 2-0-1 であり、もう一方は 1-0-1 です。
- セレクター 3 と 4 は、リンクの文字色のスタイル設定をめぐって競合しています。2 つ目のセレクターが勝って、テキストを白にします。これは、要素セレクターが 1 つ少ないものの、足りないセレクターをクラスセレクターに入れ替えたためで、これはいくつの要素セレクターよりも比重が大きいからです。勝者の詳細度は、1-1-3 対 1-0-4 です。
- セレクター 5 ~ 7 は、マウスを当てたときのリンクの境界線のスタイル設定をめぐって競合しています。セレクター 6 は、0-2-3 対 0-2-4 の詳細度でセレクター 5 に明らかに負けています。セレクター 6 には、連鎖する要素のセレクターが 1 つ少ないからです。しかし、セレクター 7 はセレクター 5 と同じ数のサブセレクターを持ちますが、要素がクラスセレクターに置き換えられているため、セレクター 5 と 6 の両方に勝っています。つまり、0-3-3 の詳細度が 0-2-3、0-2-4 に対して勝利します。
メモ: セレクターの種類ごとにはそれぞれレベルの詳細度があり、より低い詳細度レベルのセレクターによって上書きすることはできません。例えば 100 万 のクラスセレクターを組み合わせても、1 つ の ID セレクターのルールを上書きすることはできません。
詳細度を評価する最良の方法は、詳細度の高いものから始めて、必要に応じて低いものへ移動しながら、個別に点数をつけることです。ある列のセレクターのスコアが同点である場合のみ、次の列を評価する必要があります。そうでない場合は、詳細度の低いセレクターは詳細度の高いセレクターを上書きすることができないため、無視することができます。
ID とクラス
ID セレクターは高い詳細度を持っています。つまり、 ID セレクターに一致するスタイルが適用されると、クラスや要素型セレクターなど、他のセレクターに基づいて適用されるスタイルが上書きされるということです。 ID はページに 1 つしか存在できず、 ID セレクターの詳細度が高いことから、 ID ではなくクラスを要素に追加するほうが望ましいです。
ID を使用することが、その要素を対象とする唯一の方法である場合、例えば、マークアップにアクセスできず、編集できない場合などには、 p[id="header"]
のように、 ID を属性セレクター内で使用することを検討してください。
インラインスタイル
インラインスタイル、つまり style
属性内のスタイル宣言は、その詳細度に関わらず、すべての通常のスタイルよりも優先されます。このような宣言はセレクターがありませんが、その固有性は 1-0-0-0 と解釈され、セレクターにいくつの ID があっても、常に他のどの詳細度よりも高い重みを持ちます。
!important
インラインスタイルであっても、上記のすべての計算を覆すために使用できる特別なCSSがあります - !important
フラグです。しかし、使用する際にはとても注意が必要です。このフラグは、個々のプロパティと値のペアを最も詳細なルールとするために使用され、それによって通常のインラインスタイルを含むカスケードのルールが上書きされます。
メモ: !important
フラグの存在を知っておくと、他の人のコードでこのフラグに出会ったときに、それが何であるかを知ることができるので便利です。しかし、絶対に必要でない限り使用しないことを強くお勧めします。 !important
フラグはカスケードの通常の動作方法を変更するので、特に大きなスタイルシートでは、CSS の問題をデバッグするのが実に作業しづらくなる可能性があります。
例を見てみましょう。2 つの段落があり、そのうちの 1 つ には ID がついています。
<p class="better">This is a paragraph.</p>
<p class="better" id="winning">One selector to rule them all!</p>
#winning {
background-color: red;
border: 1px solid black;
}
.better {
background-color: gray;
border: none !important;
}
p {
background-color: blue;
color: white;
padding: 5px;
}
何が起きているのかを見てみましょう。理解しにくい場合は、いくつかのプロパティを削除しながら、どうなるか見てみてください。
- 3 番目のルールでは
color
とpadding
が適用されていますが、background-color
は適用されていないことようです。なぜでしょうか?ソースオーダーの後の方は、普通は前の方のルールをオーバーライドするため、その観点では 3 つ すべてが適用されるはずです。 - とはいえ、クラスセレクターは要素セレクターよりも詳細度が高いため前者のルールが優先されます。
- 両方の要素には
better
というclass
がありますが、2 番目 の要素にはwinning
というid
もあります。 ID はクラスよりも詳細度が高いため(ページ上では ID を持つ要素は一意に 1 つ しか置けないのに対し、同じクラスを持つ多くの要素がありえるため、ID セレクターの方がはるかに限定的になります)、2 番目の要素には赤い背景色と 1 ピクセルの黒い境界線が適用され、最初の要素についてはクラスで指定されたように灰色の背景色となり、境界線は消えます。 - 2 番目 の要素は赤い背景が適用されていますが、あるはずの境界線はありません。なぜでしょうか? 2 つ目のルールに
!important
フラグがあるためです。border: none
の後に!important
フラグを追加すると、ID セレクターの方がより高い詳細度であっても、この宣言が前のルールのborder
値に勝利するということです。
メモ: important 宣言を上書きする唯一の方法は、同じ詳細度を持つ別の important 宣言をソースの順番で後に記載するか、より高い詳細度を持つものを記載するか、前のカスケードレイヤーに important 宣言を記載することです(まだ、カスケードレイヤーについて触れていません)。
!important
フラグを使用しなければならない状況の 1 つ としては、コアにある CSS モジュールを編集できない CMS で作業している等で、他の方法では難しい場合にスタイルを実際にオーバーライドしたい場合等があります。でも本当に、回避できる場合は使用しないでください。
CSS の位置の効果
最後に、CSS 宣言の優先順位は、それがどのスタイルシートとカスケードレイヤーで指定されるかに依存することに注意することが重要です。
ユーザーがカスタムスタイルシートを設定して、開発者のスタイルを上書きすることは可能です。例えば、視覚的な障碍を持つユーザーが、アクセスするすべてのウェブページのフォントサイズを通常の 2 倍に設定して、読みやすくすることができます。
カスケードレイヤーで開発者のスタイルを宣言することも可能です。レイヤーで宣言されたスタイルをレイヤー以外のスタイルで上書きしたり、先に宣言されたレイヤーのスタイルを後のレイヤーで宣言されたスタイルで上書きしたりすることが可能です。例えば、開発者としてサードパーティのスタイルシートを編集することはできないかもしれませんが、外部スタイルシートをカスケードレイヤーにインポートすることで、サードパーティのセレクターの詳細度を気にせず、インポートしたスタイルをすべて簡単に上書きすることができます。
宣言の上書きの順
競合する宣言は、以下の順序で適用され、後に適用されたものが先に適用されたものを上書きします。
- ユーザーエージェントのスタイルシートにおける宣言(例えば、他にスタイルが設定されていない場合に使用される、ブラウザーの既定スタイル)。
- ユーザースタイルシート(ユーザーが設定したカスタムスタイル)内の通常の宣言。
- 作成者スタイルシートでの通常の宣言(これはウェブ開発者である私たちが設定したスタイルです)。
- 作成者スタイルシートにおける important 宣言。
- ユーザースタイルシートにおける important 宣言。
- ユーザーエージェントスタイルシートにおける important 宣言。
メモ:
優先順位は !important
でフラグづけされたスタイルでは逆転します。ウェブ開発者のスタイルシートがユーザーのスタイルシートを上書きするのは理にかなっているので、デザインは意図したとおりに保たれます。しかし、上記のように、ユーザーがウェブ開発者のスタイルを上書きするのに有益な理由がある場合もあり、その場合はルールに !important
を使用して実現することができます。
スキルテスト
この記事の最後まで達しましたが、最も大事な情報を覚えていますか?次に移動する前に、この情報を覚えているか検証するテストがあります。スキルテスト: カスケードを見てください。