レイアウトに役立つ疑似クラス
ここ数年でCSSには多くの疑似クラスが追加され、かつてのようにごく少数のブラウザしか実装していないという状況ではなくなった。今日はいくつかのCSS疑似クラスを紹介し、実際のユースケースを通してレイアウトにどう役立てるかを解説したいと思う。
:is:where:has:lang:focus-within:first-childと:last-child
:is
これまで複数のセレクタに同じCSSルールを適用したい場合、以下のように書いていた:
.a-class,
.b-class {
font-size: 25px;
}
この書き方でも何の問題もないが、:is が登場したことで、宣言を以下のように簡略化できるようになった:
:is(.a-class, .b-class) {
font-size: 25px;
}
適用したいセレクタが多い場合、:is を使ったほうが可読性が高くなる。まだWorking Draft(草案段階)ではあるものの、Can I use のデータを見ると、98.1% のユーザーが利用可能だ。
単一のクラスだけでなく、他のセレクタと組み合わせても使用できる:
:is(.a-class, .b-class) a:hover {
opacity: 0.8;
}
:where
:where の役割は :is と同じだ。ただし詳細度(優先度)の扱いに違いがある。:where を使用する場合、詳細度は加算されず、詳細度は * と同じ(0)になる。しかし :is は異なり、リスト内で最も詳細度が高いセレクタの重みが最終的な詳細度として適用される。
Hello
この例では、#page が含まれているため詳細度がクラスセレクタよりも高くなり、最終的な文字色は赤になる。もし :where を使った場合、中のセレクタに詳細度は付かない。
Hello
そのため、最終的に適用される色は黄色になる。 以上の結果から、ユースケースは以下のように分けられる:
:where()を使ってグローバルスタイル(リセットCSSなど)を定義する。こうすることで後から簡単に上書きできる:is()を使って複数のセレクタにローカルスタイルを適用し、すべてのセレクタに同じ詳細度を持たせる
:has
:has は、条件に合致する子要素が存在するときに、親要素にマッチさせることができる。説明を聞くと少しややこしいかもしれないが、実例を見ればより分かりやすいはずだ。実用的なケースの1つが「子要素がフォーカスされたときに親要素のスタイルを変えたい」という場面だ。
検索機能を実装する際、検索アイコンを入力欄の横に配置することがある。入力欄がフォーカスされたとき、入力欄だけでなくアイコンを含む入力エリア全体にフォーカススタイルを適用したい場合がある。このとき、input:focus だけでは目的のスタイルを実現できない。
以前の解決策としては、CSSセレクタが効くようにDOM構造を変更するか、JavaScriptでfocusイベントを監視して親要素にクラスを追加し、inputがフォーカスされたことを伝える必要があった。しかし :has を使えば、DOM構造を変えることなくCSSだけで直接実現できる。
Focus input!
これにより、inputがフォーカスされたときに .form:has(input:focus) が有効になり、親要素にスタイルが適用される。「子要素が条件に合致したときに親要素を選択する」ということは、フロントエンドエンジニアにとって長年の悲願だったが、ようやく大きな一歩を踏み出したと言える。
:has には他にも用途があり、特に「子要素がある条件を満たしたときにこのセレクタを適用したい」という場面で活躍する。
例えばテーマを切り替える際、body:has(input[type="checkbox"]:checked) を使えば、JavaScriptを使わずにカラーテーマを切り替えることができ、同時に適切なDOM構造も維持できる。
:has のユースケースについては、webkit のブログにより多くの事例が紹介されているので参考にしてほしい。:has は比較的新しい疑似クラスであり、ブラウザのサポート率はまだそこまで高くない(82.92%)ため、導入する際はフォールバックを用意しておいたほうがいいかもしれない。
:focus-within
経験のあるフロントエンドエンジニアなら、先ほどの例は実は :focus-within で代替できることに気づいたかもしれない。:focus-within は子要素がフォーカスされたときにマッチするため、親要素のスタイルを変更できる。
例えば、フォーカス時にフォーム全体に box-shadow を適用したい場合は、このように書ける:
ただし、:focus-within はフォーカス状態にしか適用できず、多様な条件指定はできない。フォーカス以外の状態にマッチさせたい場合は、より柔軟な :has を使うとよい。
:lang
多言語サイトを実装する際、現在のサイトの言語を示すために <html> に lang 属性を付与する。言語ごとに異なるフォントを表示したい場合、:lang を使わないと以下のように書く必要があるかもしれない:
html[lang="zh"] p { font-family: var(--font-zh); }
html[lang="en"] p { font-family: var(--font-en); }
これだと少々冗長だが、:lang を使えばセレクタの構文をシンプルにできる。
p:lang(en) {
font-family: var(--font-en);
}
p:lang(zh) {
font-family: var(--font-zh);
}
:first-child と :last-child
これら2つは非常によく使われる疑似クラスだが、僕が見る限り、知らないフロントエンドエンジニアもまだ結構いるようだ。この2つの疑似クラスは最初の子要素または最後の子要素を指定できる。最も一般的なユースケースは、margin-right を指定しつつ最後の要素には適用したくない場合で、そのときはこのように書くことができる。
.tab {
margin-right: 8px;
}
.tab:last-child {
margin-right: 0;
}
また、:not と組み合わせて簡略化することもできる:
.tag:not(:last-child) {
margin-right: 8px;
}
なぜJavaScriptを使わないのか?
ユーザーの中には必ずしもJavaScriptを有効にしているとは限らない。特にブログなどの静的コンテンツが中心のWebサイトで、閲覧体験を損ないたくない場合、今回紹介した疑似クラスがレイアウト上の課題を解決する助けになる。シンプルにできるなら、誰だって複雑な方法は選びたくないはずだ。
関連記事
- フロントエンドで画像を扱う際に注意すべきこと Jake Archibaldの記事を起点に、現代のレスポンシブ画像の書き方を整理する。なぜwidth/heightを付ける必要があるのか、CSSのaspect-ratioはいつ使うべきか、AVIFとWebPの選び方、そしてpicture/source/srcsetを使ったモバイル向け画像の切り替えについて。
- CSS field-sizing — たった1行のCSSでフォーム要素を自動リサイズする かつてtextareaの自動高さ調整は、JavaScriptでscrollHeightを監視するしかなかった。しかしCSSのfield-sizing: contentなら、わずか1行で代替でき、textarea、input、selectに対応している。本記事では従来のやり方のペインポイントと、field-sizingの使い方をまとめる。
- リンクの下線をもっと見栄え良くする:text-underline-offset デフォルトでは下線と文字が近すぎて、このスタイルを好まないデザイナーもいるし、僕自身もあまり綺麗ではないと感じていた。
- なぜウェブで Pixel Perfect を追求すべきではないのか Pixel Perfect はそれが本当に重要な場合にのみ意識すべきであり、そうでなければ往々にしてお互い損をする結果に陥ってしまう。