Neat Design Journal

新しい擬似クラス:isと:whereの使い方

新しい擬似クラス:isと:whereの使い方

今回は、CSS Selectors Level 4で仕様策定された比較的新しい擬似クラスの:is:whereの使い方についてご紹介したいと思います。どちらも複数のセレクタをまとめることができるので便利なんですが、違いがよく分からなかったのでちょっと調べてみました。

擬似クラスとは

MDNの解説を引用すると、

CSSの擬似クラス (pseudo-classes) は、セレクターに付加するキーワードであり、選択された要素に対して特定の状態を指定します。

と書いてあります。よく分からないのでもう少し読んでみると、

例えば、擬似クラス:hoverを使用すると、ユーザーのポインターがボタンの上に乗ったときにボタンを選択し、この選択されたボタンをスタイル設定することができます。

と続き、

/* ユーザーのポインターが乗っているすべてのボタン */
button:hover {
  color: blue;
}

というコード例が掲載されています。これでなんとなく分かりました。特定の状態を表すのが擬似クラスで、擬似クラスにスタイルを指定することでその状態の時だけにそのスタイルが適用されるということでしょう。

擬似クラスには他にも:activeとか:visited:checkedとか:first-childなどたくさん種類があります。

擬似クラス - CSS: カスケーディングスタイルシート | MDN

CSS の擬似クラス (pseudo-classes) は、セレクターに付加するキーワードであり、選択された要素に対して特定の状態を指定します。例えば、擬似クラス :hover を使用すると、ユーザーのポインターがボタンの上に乗ったときにボタンを選択し、この選択されたボタンをスタイル設定することができます。

擬似クラス - CSS: カスケーディングスタイルシート | MDNのファビコン
https://developer.mozilla.org/ja/docs/Web/CSS/Pseudo-classes
擬似クラス - CSS: カスケーディングスタイルシート | MDNのサムネイル

今回ご紹介する:iswhereもこの擬似クラスに分類されます。

:is

:isは擬似クラスの中でも関数擬似クラスと言われているようです。これは:whereも同じですね。

:isはCSSの擬似クラス関数で、セレクターのリストを引数に取り、リスト中のセレクターの何れか一つに当てはまる要素をすべて選択します。数多くのセレクターを小さくまとめて書くのに便利です。

ということで、複数のセレクターを一つにまとめることができるので、コードの見通しがよくなるというメリットがあります。具体的に見ていきましょう。

<article>
    <h2>...</h2>
    ...
    <h3>...</h3>
    ...
    <h4>...</h4>
</article>

article要素以下にh2h3h4があり、これらに共通のスタイルを指定したい時、通常であれば

article h2,
article h3,
article h4 {
    line-height: 1.5;
}

このようなコードを書くと思いますが、:isを使うと以下のように書くことができます。

article :is(h2, h3, h4) {
    line-height: 1.5;
}

セレクタ部分が一行で済むようになりますので、見やすく整理しやすくなります。

ちなみにこれをCSSネストで書くと、

article {
    :is(h2, h3, h4) {
        line-height: 1.5;
    }
}

こうなります。こうするとさらに親子関係が明確になって分かりやすいかと思います。

もう一つ例を挙げます。

<nav>
    <a>...</a>
</nav>

<aside>
    <a>...</a>
</aside>

<section class="sidebar">
    <a>...</a>
</section>

nav要素の子要素aaside要素の子要素a.sidebar要素の子要素aにそれぞれ同じスタイルを当てたい時の通常のコードは、

nav a,
aside a,
.sidebar a {
    text-decoration: none;
}

こうなりますが、:isを使うと、

:is(nav, aside, .sidebar) a {
    text-decoration: none;
}

このように簡潔になります。そしてしつこいですがこれもネストして書くと、

:is(nav, aside, .sidebar) {
    & a {
        text-decoration: none;
    }
}

こうなります。覚えてしまえば簡単なので無駄に使いたくなりますね。

:is() - CSS: カスケーディングスタイルシート | MDN

:is() は CSS の擬似クラス関数で、セレクターのリストを引数に取り、リスト中のセレクターのいずれか一つに当てはまる要素をすべて選択します。数多くのセレクターを小さくまとめて書くのに便利です。

:is() - CSS: カスケーディングスタイルシート | MDNのファビコン
https://developer.mozilla.org/ja/docs/Web/CSS/:is
:is() - CSS: カスケーディングスタイルシート | MDNのサムネイル

:where

続いては:whereですが、基本的な使い方は:isと全く同じです。そのため、先ほどの2つの例を:whereで書き換えてみると、

/* 最初の例 */
article {
    :where(h2, h3, h4) {
        line-height: 1.5;
    }
}

/* 次の例 */
:where(nav, aside, .sidebar) {
    & a {
        text-decoration: none;
    }
}

このようになり、全く同じ使い方になることがお分かりいただけるかと思います。

:whereはCSSの擬似クラス関数で、セレクターリストを引数として取り、列挙されたセレクターのうちの何れかに当てはまるすべての要素を選択します。

MDNの解説文もほぼ同じ内容です。

:isと:whereの違い

書き方も使い方も全く同じであるならば、両者の違いは一体何なのでしょうか。MDNの解説を読み進めていくと、

:where():is()の違いは、:where()詳細度が常に0であるのに対して、:is()引数内で最も詳細度の高いセレクターの詳細度を取ります

とのことです。

詳細度とは

詳しくはMDNのサイトをお読みいただくとして、語弊を恐れず簡潔に言うと詳細度が大きい方のスタイルが適用されるということです。指定しているセレクタの数や種類によって詳細度は変わり、スタイルの衝突が起きると両者のうち詳細度が大きい方のスタイルが適用されることになります。

詳細度 - CSS: カスケーディングスタイルシート | MDN

詳細度 (Specificity) は、ある要素に最も関連性の高い CSS 宣言を決定するためにブラウザーが使用するアルゴリズムで、これによって、その要素に使用するプロパティ値が決定されます。詳細度のアルゴリズムは、CSS セレクターの重みを計算し、競合する CSS 宣言の中からどのルールを要素に適用するかを決定します。

詳細度 - CSS: カスケーディングスタイルシート | MDNのファビコン
https://developer.mozilla.org/ja/docs/Web/CSS/CSS_cascade/Specificity
詳細度 - CSS: カスケーディングスタイルシート | MDNのサムネイル

:whereの詳細度は常にゼロなので、引数に指定したセレクタがどんなに大きかろうが全体の詳細度はゼロとして処理されます。

:whereの使いどころ

では、詳細度が常にゼロになることのメリットは何なのでしょうか。これがよく分かりませんでした。詳細度が常にゼロなら使う意味ないと思っていましたが、ある時、普段よく見ているサイトで読んだ内容が目から鱗でした。

この中で詳細度を低く抑えるという項目があるのですが、リセットCSSの中で使うことで後から容易に上書きできるように敢えて:whereを使って指定している、ということが書かれてありました。これにはなるほどねと感心させられました。リセットCSSって、UAスタイルシートで指定されているブラウザの初期値を一旦リセットして再度スタイリングし直すためのもの、要するに後から必ず上書きするものなんですね。なので上書きする時に余計な詳細度について考える必要をなくすため、:whereを使っているという内容でした。頭いいですね。

当ブログのリセット部分

これを知ってから、当ブログのリセット部分には:whereを使うようにしました。

/* generic block-level elements */
:where(p) {
    margin-block-start: unset;
    margin-block-end: unset;
}
:where(blockquote) {
    margin-block-start: unset;
    margin-block-end: unset;
    margin-inline-start: unset;
    margin-inline-end: unset;
}
:where(figure) {
    margin-block-start: unset;
    margin-block-end: unset;
    margin-inline-start: unset;
    margin-inline-end: unset;
}
:where(img) {
    max-inline-size: 100%;
    block-size: auto;
    vertical-align: bottom;
}
/* heading elements */
:where(h1, h2, h3, h4, h5, h6) {
    margin-block-start: unset;
    margin-block-end: unset;
}
/* lists */
:where(ul, ol) {
    margin-block-start: unset;
    margin-block-end: unset;
    padding-inline-start: unset;
}
:where(dd) {
    margin-inline-start: unset;
}
:where(dl) {
    margin-block-start: unset;
    margin-block-end: unset;
}

こちらの詳細はまた別の機会に投稿したいと思います。

最後に

擬似クラスは他にもたくさん種類があり、使わないことはないくらいお世話になっています。まだまだ他の擬似クラスも勉強していきたいなと思います。