様々な擬似クラスの登場により、それぞれの用途に応じて柔軟に対象を絞り込んでスタイルを適用することが容易になりました。
これまでJavaScriptで実装していた機能の多くが、CSSでも実現可能になった部分も増えています。
しかし、依然としてJavaScriptで操作した方が見通しが良かったり、CSSだけではどうしても対応できないケースもあるので、それらについてまとめてみます。
<div class="container">
<ul>
<li class="item">A (li.item)</li>
<li class="item">B (li.item)</li>
<li class="item">C (li.item)</li>
<li>D (li)</li>
</ul>
<p>E (p)</p>
<p class="item">F (p.item)</p>
<ul>
<li class="item">G (li.item)</li>
<li>H (li)</li>
<li class="item">I (li.item)</li>
<li class="item">J (li.item)</li>
</ul>
<ul>
<li>K (li)</li>
<li>L (li)</li>
<li>M (li)</li>
</ul>
<ul>
<li class="item">N (li.item)</li>
<li class="item">O (li.item)</li>
</ul>
<p>P (p)</p>
<p class="item">Q (p.item)</p>
<ul>
<li>R (li)</li>
<li class="item">S (li.item)</li>
<li>T (li)</li>
</ul>
</div>
.container {
border: 1px solid black;
font-family: monospace;
width: 200px;
}
See the Pen Untitled by Yoruaki (@yoruaki) on CodePen.
コードの簡潔さと可読性を考慮して、便宜上.container
で全体を囲んでいます。
そのため、.container
の兄弟要素にはスタイルが適用されないなどの制約がありますが、本記事では擬似クラスの適用範囲や動作確認を視覚的に分かりやすくすることを目的としています。
全ての.item
にスタイルを適用したい
.container .item {
background-color: red;
}
document.querySelectorAll('.item').forEach(function(element) {
element.style.backgroundColor = 'red';
});
CSSで対応できるものは、CSSに任せましょう。
最初の.item
にスタイルを適用したい
.container .item:first-of-type {
background-color: red;
}
このセレクタは、各親要素の中で最初に出現する要素かつ.item
だった場合に適用されるため、複数の親要素がある場合や、最初に.item
が来ない場合には意図した結果にならないことがあります。
つまり「最初の要素ではないかつ最初に出現する.item
」には適用されません。
今回のケースだとA、G、Nに適用され、FとSには適用されません。
最初の要素に.item
が出現することが確定しているなら、そのままCSSで指定しましょう。
各要素内で最初に出現する.item
にスタイルを適用したい
CSSでは「最初の要素」は取得できても、「最初に出現する特定の要素」は取得できません。
そのため、この場合はJavaScriptでの実装が必要です。
document.querySelectorAll('.container > *').forEach(parent => {
const firstItem = parent.querySelector('.item');
if (firstItem) {
firstItem.style.backgroundColor = 'red';
}
if (parent.classList.contains('item')) {
parent.style.backgroundColor = 'red';
}
});
ドキュメント全体で最初に出現する.item
だけにスタイルを適用したい
あまりないとは思いますが、ドキュメント全体で最初の.item
だけにスタイルを適用したい場合は、JavaScriptで実装するしかありません。
const firstItem = document.querySelector('.item');
if (firstItem) {
firstItem.style.backgroundColor = 'red';
}
最後の.item
にスタイルを適用したい
.container .item:last-of-type {
background-color: red;
}
こちらも.item:first-of-type
セレクタと同じく、「最後の要素ではないかつ最後に出現する.item
」には適用されません。
このケースも、本来の意図はC、Sにも適用させたいものと想像できます。
もしこのままでOKなら、そのままCSSで指定しましょう。
各要素内で最後に出現する.item
にスタイルを適用したい
こちらも同じく、CSSでは「最後の要素」は取得できても、「最後に出現する特定の要素」は取得できません。
そのため、この場合もJavaScriptが必要です。
document.querySelectorAll('.container > *').forEach(parent => {
const childItems = parent.querySelectorAll('.item');
if (childItems.length > 0) {
const lastItem = childItems[childItems.length - 1];
lastItem.style.backgroundColor = 'red';
}
});
const parentItems = document.querySelectorAll('.container > .item');
const lastParentItem = parentItems[parentItems.length - 1];
if (lastParentItem) {
lastParentItem.style.backgroundColor = 'red';
}
ドキュメント全体で最後に出現する.item
だけにスタイルを適用したい
こちらもあまりないとは思いますが、ドキュメント全体で最後の.item
だけにスタイルを適用したい場合も、JavaScriptで実装するしかありません。
const items = document.querySelectorAll('.item');
if (items.length > 0) {
const lastItem = items[items.length - 1];
lastItem.style.backgroundColor = 'red';
}
特定の要素が.item
だったらスタイルを適用したい
CSSで指定しましょう。
.container p.item {
background-color: red;
}
.item
を持っている親要素にスタイルを適用したい
CSSで指定しましょう。
.container :has(.item) {
background-color: red;
}
.item
を持っていない親要素にスタイルを適用したい
.container > *:not(:has(.item)) {
background-color: red;
}
しかしこの場合、そもそも子要素を持っていない要素にもスタイルが適用されてしまいます。
これが意図した通りならそのままCSSで指定しましょう。
CSSには、その要素が子要素を持っているかどうかを判定する機能はありません。その場合もJavaScriptで実装しましょう。
document.querySelectorAll('.container > *').forEach(element => {
if (element.children.length > 0 && !element.querySelector('.item')) {
element.style.backgroundColor = 'red';
}
});
兄弟要素がない要素にスタイルを適用したい
以下のHTMLで、A、Dに適用したい場合ですね。
<div class="container">
<ul>
<li>A</li>
</ul>
<ul>
<li>B</li>
<li>C</li>
</ul>
<ul>
<li>D</li>
</ul>
<p>E</p>
</div>
CSSで指定しましょう。
.container :only-child {
background-color: red;
}
同じ型で兄弟要素がない要素にスタイルを適用したい
上記の例だと、A、D、Eに適用したい場合ですね。
こちらもCSSで指定しましょう。
.container :only-of-type {
background-color: red;
}
only-child
でEに適用されなかった理由は、p
要素はul
要素と型違いの兄弟だからです。
逆にonly-of-type
は、兄弟だけど型が違うからonlyだったのです。
兄弟要素の中で自分だけ.item
を持っている要素にスタイルを適用したい
上記の例だと、Sに適用させたいパターンですね。
この場合、Sは.item:only-child
でも.item:only-of-type
でも指定できません。
ここはJavaScriptが適しています。
document.querySelectorAll('.item').forEach(item => {
const siblings = Array.from(item.parentElement.children);
const itemSiblings = siblings.filter(sibling => sibling.classList.contains('item'));
if (itemSiblings.length === 1) {
item.style.backgroundColor = 'red';
}
});
中身が空の.item
にスタイルを適用したい
CSSで指定しましょう。
.container .item:empty {
background-color: red;
}
各セクションのまとめ
ケース | CSSで対応可能 | JavaScriptが必要 | 補足説明 |
---|---|---|---|
全ての.item にスタイルを適用したい |
✅ | ❌ | CSSで十分 |
最初の.item にスタイルを適用したい |
✅ | ❌ |
:first-of-type を使用 |
各要素内で最初に出現する.item にスタイルを適用したい |
❌ | ✅ | CSSでは難しい |
ドキュメント全体で最初に出現する.item だけにスタイルを適用したい |
❌ | ✅ | CSSでは難しい |
最後の.item にスタイルを適用したい |
✅ | ❌ |
:last-of-type を使用 |
各要素内で最後に出現する.item にスタイルを適用したい |
❌ | ✅ | CSSでは難しい |
ドキュメント全体で最後に出現する.item だけにスタイルを適用したい |
❌ | ✅ | CSSでは難しい |
特定の要素が.item だったらスタイルを適用したい |
✅ | ❌ | セレクタで直接指定可能 |
.item を持っている親要素にスタイルを適用したい |
✅ | ❌ |
:has() を使用 |
.item を持っていない親要素にスタイルを適用したい |
✅ | ✅ |
:not() と:has() を使用※子要素の有無も判定するならJSを使用 |
兄弟要素がない要素にスタイルを適用したい | ✅ | ❌ |
:only-child を使用 |
同じ型で兄弟要素がない要素にスタイルを適用したい | ✅ | ❌ |
:only-of-type を使用 |
兄弟要素の中で自分だけ.item を持っている要素にスタイルを適用したい |
❌ | ✅ | CSSでは難しい |
中身が空の.item にスタイルを適用したい |
✅ | ❌ |
:empty を使用 |
まとめ
以上、CSSの擬似クラスによるスタイル指定と、代替となるJavaScriptの使用例のまとめでした。
今回はHTMLが動的に変更され、こちらがコントロールできない場合を想定して書きましたが、通常、それ専用のクラスを適用したりすることで多くのケースをカバーできるかと思います。
しかしながら、中にはCSSだけではどうしても対応が難しい問題もあるかと思いますので、柔軟にCSSとJavaScriptを併用することで、より効果的なスタイリングをしていきましょう。
この記事が少しでもスタイル指定の参考になれば幸いです。