CSSの:has()疑似クラスの便利な使い方のまとめ

CSSの:has()疑似クラスが主要ブラウザでサポートされ、喜んでいる人も多いと思います。今まではJavaScriptを使用しなければできなかったことが、:has()疑似クラスを使用するとさまざまなセレクタを条件式のように記述できます。

たとえば、子に画像がある場合とない場合、子の数が奇数の場合と偶数の場合、セレクタを追加したり変更することなく指定できます。また、コンテンツやフォームなどにも便利な使い方がたくさんあります。

:has()疑似クラスの便利な使い方のまとめ

:has(): the family selector
by Jhey Tompkins

下記は各ポイントを意訳したものです。
※元サイト様のライセンスに基づいて翻訳しています。基づいてというのは、貢献部分に関して同ライセンスも含みます。

先日リリースされたChrome 105, Edge 105で:has()疑似クラスはサポートされ、コンテナクエリ(@container)と:modal疑似クラスもサポートされました。

下記は各ポイントを意訳したものです。
※元サイト様のライセンスの元、翻訳しています。

はじめに

CSSの登場以来、わたし達はさまざまな意味でカスケードを使用してきました。実装に使用するCSSは「カスケードスタイルシート(Cascading Style Sheet)」を構成しています。そして、セレクタもカスケードします。横に進むこともできますが、ほとんどの場合は下に進みます。決して上に進むことはありません。

何年もの間、わたし達は上に進む「親」セレクタを求めていました。
そしてついに、:has()疑似クラスでそれが実現しました!

CSSの:has()疑似クラスとは、パラメータとして渡されたセレクタのいずれかが少なくとも一つの要素に一致する場合に、その要素を表します。

しかし、これは単なる「親」セレクタではありません。別の言い方にすると、「条件付き環境」セレクタでしょうか。うーん、ひょっと響きが違いますね。「家族」セレクタ(family selector)の方がいいかもしれません。

:has()疑似クラスのサポートブラウザ

:has()疑似クラスの使い方の前に、ブラウザのサポート状況について触れておきましょう。まだ十分なサポート状況とは言えませんが、すべてのブラウザでサポートされるのはもうすぐです。Safariでは15.4ですでにサポートされており、Chromeでは先日の105でサポートされました。Firefoxではロードマップ上にありますが、まだサポートされていません。

この記事のすべてのデモには:has()疑似クラスが使用されています。まずは、下記のデモページであなたのブラウザが:has()疑似クラスをサポートしているかどうか確認してください。

See the Pen
Is :has() supported here?
by web.dev (@web-dot-dev)
on CodePen.

2022年9月現在、:has()疑似クラスのサポート状況は、下記の通りです。

サイトのキャプチャ

:has()疑似クラスのサポート状況

:has()疑似クラスの基本的な使い方

:has()疑似クラスの基本的な使い方を次のHTMLを例に見てましょう。

.everybodyを持つ2つの兄弟要素があります。

.a-good-timeの子孫要素を含む方のセレクタはどのように記述すればよいでしょうか?

:has()疑似クラスを使用すると、それが簡単にできます。

このCSSで、.everybodyの1つ目の要素が選択され、スタイルが適用されます。

この例では、.everybodyというクラスを持つ要素がターゲットになっています。条件は、.a-good-timeを持つ子孫を持つことです。

:has()疑似クラスはこれだけではありません!
ほかにもいろいろな使い方があるので、見てましょう。

:has()疑似クラスの興味深い使用例はありましたか?
ここで注目すべきは、メンタルモデルを壊すことを奨励していることです。「これらのスタイルに別の方法でアプローチできないか」と考えさせられます。

:has()疑似クラスの便利な使い方

では:has()疑似クラスを実際にどのように使用するのか、いくつか例を見てみましょう。

カードの実装

まずはシンプルなカードのデモを見てみましょう。
カードにはタイトルやサブタイトル、画像などのメディアなど、あらゆる情報を配置できます。カードの基本的なHTMLは下記の通りです。

実際の動作は、デモをご覧ください。

See the Pen
Basic Card
by web.dev (@web-dot-dev)
on CodePen.

カードに画像を配置したいときはどうしますか?
カードでは2つのカラムを使用できます。今まではcard--with-mediacard--two-columnsのようなクラスを作成していたかもしれません。このようなクラス名は、思いつくのが難しいだけでなく、それをずっと覚えておくのも難しいものです。

:has()疑似クラスを使用すれば、カードが何らかのメディアを持っていることを検出して、適切なスタイルを適用できます。修飾子(Modifier)のクラス名は必要ありません。

実際の動作は、デモをご覧ください。

See the Pen
Card with Media
by web.dev (@web-dot-dev)
on CodePen.

これだけではありません、もっとクリエイティブになれるはずです。特集コンテンツを表示するカードは、レイアウト内でどのように適応させますか? 下記のCSSは、特集を表示するカードをレイアウトの幅いっぱいに表示し、グリッドの最初に配置します。

実際の動作は、デモをご覧ください。

See the Pen
:has Media Card Layouts
by web.dev (@web-dot-dev)
on CodePen.

さらに、バナーがある特集カードで注目を集めるためにアニメーションさせてみます。

アニメーションは6秒間隔で動かしてみます。

実際の動作は、デモをご覧ください。

See the Pen
:has Wiggle Card
by web.dev (@web-dot-dev)
on CodePen.

:has()疑似クラスには多くの可能性があります!

フォームの実装

フォームはCSSでスタイルするのが難しいことで知られています。たとえば、入力欄とそのラベルのスタイルです。入力が有効であることを示すにはどうすればよいでしょうか?

:has()疑似クラスを使用すると、これが非常に簡単になります。:valid:invalidなど、関連するフォームの疑似クラスをフックにできます。

実際の動作は、デモをご覧ください。
有効な値と無効な値を入力し、フォーカスをオンまたはオフにしてみてください。

See the Pen
:has some <input>
by web.dev (@web-dot-dev)
on CodePen.

さらに、:has()疑似クラスを使用して、エラーメッセージを表示・非表示することもできます。Emailのフィールドグループを使用して、エラーメッセージを追加してみます。

デフォルトでは、エラーメッセージを非表示にします。

そしてフィールドが:invalidになり、かつフォーカスされていない場合、追加のクラス名を必要とせずにメッセージを表示できます。

実際の動作は、デモをご覧ください。

See the Pen
This form shows and hides its error messages
by web.dev (@web-dot-dev)
on CodePen.

さらにフォームにセンスのいいアクションを加えてみます。無効な値を入力すると、フォームグループを揺らします(※ユーザーがモーション設定していない場合)。こういったマイクロインタラクションも:has()疑似クラスを使用すると、簡単に実装できます。

See the Pen
This <input> :has an error message
by web.dev (@web-dot-dev)
on CodePen.

コンテンツの実装

コンテンツの実装は:has()疑似クラスの基本的な使い方で触れましたが、ドキュメントフローで:has()疑似クラスを使用するにはどうすればよいでしょうか?

たとえば、テキスト間にある画像のスタイルです。

この例では、画像がいくつ含まれています。画像にfigcaptionがない場合はコンテンツ内にフロートされ、figcaptionがある場合は全幅を占め、追加のマージンを与えます。
実際の動作は、デモをご覧ください。

See the Pen
An article that :has <figure>
by web.dev (@web-dot-dev)
on CodePen.

状態への反応

マークアップのある状態に対してスタイルを反応するというのはどうでしょうか。シンプルなスライド式のナビゲーションバーで見てましょう。

ナビゲーションを開くボタンがある場合はaria-expanded属性を使用するかもしれません。JavaScriptで適切な属性に変更することができます。aria-expandedtrueの場合は:has()疑似クラスで検出し、ナビゲーションバーのスタイルを更新します。JavaScriptはその役割を果たし、CSSはその情報を使用して必要なことをすることができます。マークアップをシャッフルしたり、余分なクラス名を追加する必要はありません。

実際の動作は、デモをご覧ください。

See the Pen
A site that :has a <nav>
by web.dev (@web-dot-dev)
on CodePen.

※メニューにLight dismissを追加していません。これは実装する必要があります。Chrome Canaryでデモをチェックして、新しいpopup属性がどのように役立つかを確認してください。

:has()疑似クラスは、ユーザーエラーを回避するのに役立つか

これらの例に共通するものは何でしょうか? :has()疑似クラスの使用方法を示していることを除けば、クラス名を変更・追加する必要がないことです。それぞれが新しいコンテンツを挿入し、属性を更新しています。これはユーザーのミスを軽減するという意味で、:has()疑似クラスの大きな利点です。

:has()疑似クラスを使用すると、CSSはDOMの変更に対応する責任を負うことができます。JavaScriptでクラス名を変更する必要がないため、デベロッパーのミスも減らすことができます。クラス名をタイプミスして、オブジェクトのルックアップを保持しなければならなかったことは、誰もが経験したことです。

これは興味深い考え方で、よりクリーンなマークアップ、より少ないコードにつながるのでしょうか? JavaScriptはあまり使用していないので、JavaScriptは少なくなります。HTMLはcard card--has-mediaのようなクラスが必要なくなるので、HTMLも少なくなります。

枠にとらわれない考え方

前述したように、:has()疑似クラスはメンタルモデルを壊すことを促します。これは、いろいろなことに挑戦できるチャンスです。CSSだけでゲームの仕組みを作ることも、限界を超える方法の一つです。たとえば、フォームとCSSを使用してステップベースのメカニズムを作ることができます。

実際の動作は、デモをご覧ください。

See the Pen
:has a game mechanic 🕹
by web.dev (@web-dot-dev)
on CodePen.

さらに、興味深い可能性を広げてくれます。
上記を利用して、フォームをtransformで次々に水平移動、トラバースさせることもできます。下記のデモを見るときは、右上の「Edit on CodePen」をクリックして別タブで開くのがベストです。

See the Pen
:has transforming <form> items 🚀
by web.dev (@web-dot-dev)
on CodePen.

またゲームとして、イライラ棒はいかがですか?
仕組みは:has()疑似クラスを使用すると簡単に作成できます。ワイヤーにカーソルが触れると、ゲームオーバーになります。+~を組み合わせて作成することもできますが、:has()疑似クラスを使用すればマークアップのトリックを使用せずに同じ結果を得ることができます。このデモも右上の「Edit on CodePen」をクリックして別タブで開くのがベストです。

See the Pen
Do you :has a steady hand? 👋 [Pure CSS Game]
by web.dev (@web-dot-dev)
on CodePen.

これらをすぐに実運用することはないと思いますが、:has()疑似クラスの可能性を探るにはよい方法です。ほかにも、:has()疑似クラスをチェーンさせることもできます。

この記事で紹介したデモは、:has()疑似クラスによって可能になった興味深い事例を示したものです。プラットフォームの新機能は、特にインタラクションが関係するものは潜在的なアクセシビリティの問題の観点から慎重に評価する必要があります。

:has()疑似クラスのパフォーマンスと制限

その前に、:has()疑似クラスでできないことは何でしょうか? :has()疑似クラスにはいくつかの制約があります。主なものは、パフォーマンスへの影響です。

  • :has():has()の直結はできません。しかし、チェーンはできます。
  • :has()内で疑似要素は使用できません。
  • 合体選択子のみを受け入れる擬似要素・疑似クラス内で:has()は使用できません。
  • 疑似要素の後に:has()は使用できません。
  • :visitedの使用は常にfalseになります。

:has()疑似クラスのパフォーマンスについては、:has() performance snapshotsをご覧ください。実装に関するこれらの洞察と詳細を共有してくれたByungwooに感謝します。

終わりに

:has()疑似クラスの準備をしておきましょう。
この疑似クラスは、CSSへの取り組み方法を一変させます!

すべてのデモは、CodePenのコレクションで利用できます。

サイトのキャプチャ

:has(): The Family Selector -CodePen

sponsors

top of page

©2024 coliss