11
3

More than 3 years have passed since last update.

:where()セレクタはCSSの「詳細度戦争」を終結させてくれるかもしれない

Last updated at Posted at 2020-12-23

Firefox 78からCSSのwhereセレクタが正式実装された。
先進的な機能を搭載したMozillaに称賛を送りたい。FireFoxを使い続けてよかった…
少し遅れて、Safari 14でも使用可能とのこと。Macは持っていないので、iPhoneで確認しようっと。
Chromeでも、Experimental Web Platform featuresフラグをオンにすると体験できる。チェックしてみたところ、以下で紹介する例では大きな問題が発生していない。バージョン88で正式実装予定とのこと1
Chromeバージョン88で、遂にwhereセレクタが正式実装された。これでメジャーなブラウザでの対応が一通り完了したことになる。CSS新時代の幕開けである。
さて、今回の記事ではそのwhereセレクタが持つ真の意味を説明していきたい。
それは、今までWebデザイナーを苦しめてきたCSS仕様の呪縛からの解放である。

CSSの呪縛とは

すなわち「詳細度(Specificity)」だ。
CSSの黎明期から存在するこの規則は、むしろCSSの特徴であるカスケードを邪魔する機能があったのではないかと筆者は思う。

問題点:物理的な配置指定と詳細度の高さを単純に同一視している

例えば次のようなhtmlにCSSを追加する場合を考える。

company.html
<dl class="company_info">
  <dt>会社名</dt>
  <dd>株式会社ケイケイ(KK.inc)</dd>
  <dt>資本金</dt>
  <dd class="capital">8,000万円</dd>
</dl>
company.css
.company_info {
  display: grid;
  grid-template-columns: 5em auto;
}
.company_info dt {
  font-weight: bold;
  color: #400;
}
.company_info dt::after {
  content: ":"
}
.company_info dd {
  color: #b00;
  margin: 0;
}
.capital {
  color: gold;
  background: black;
}

いろいろ突っ込みどころがある2コードだが、説明のためだと思って笑って許していただきたい。

ここで問題。<dd class="capital">の文字色(color)は何色だろうか? CSSに日常的に触っている人なら即答できるはずだ。

答えは#b00、赤茶色である。詳細度は.company_info dd(0:1:1) > .capital(0:1:0)だからである。

そんなことは分かり切っている。ここで気になるのは、このコードを書いた人間はどのように考えてこんなコードにしたのだろうということだ。
おそらく彼(女)は詳細度を引き上げるつもりでこんな書き方をしたのではなかろう。companyというクラスのdlに含まれるddにはとりあえずcolorがついてほしいというだけのはずである。そして色を後から書き換えるユーティリティクラスとして.capitalを作った。だが残念なことに作成者はCSSの法則を理解していなかった。
https://codepen.io/NagayamaToshiaki/pen/wvGKJxy

この問題をどのように解決するか、実例を挙げていく。

解決策1:!important を使用する

例えば、cssを以下の様にすれば.capitalは作成者の意図通りの色になるはずである。

company.css
/* 前略 */
.company dd {
  color: #b00;
  margin: 0;
}
.capital {
  color: gold !important;
  background: black;
}

!importantは「それが宣言リストのどこであっても、この宣言は CSS 内で作られたその他の宣言を上書き」する(MDNより)。これはとりあえず問題を回避するには十分な解法である。.capitalがユーティリティクラスのため、これを上書きしたいという要求はまずないだろう。
しかし、!importantは諸刃の剣である。CSSの順序や詳細度を無視するため、「最強の矛と最強の盾」問題が発生する。要はプログラムの規模がでかくなると管理が行き届かなくなる。それに各属性にいちいち!importantを付けなければならないので、指定する必要がある属性が増えるほど付け忘れたりと、ケアレスミスの温床になる。

解決策2:.capitalの詳細度を上げる

例えばdd.capitalとすれば、.company_info ddと詳細度が等しくなり、ソースコードの順番からdd.capitalのほうが勝つ。ほかにはコンテナ(親要素)の要素名やクラス名を記載する方法もある。なお、.capital.capitalと繰り返しても詳細度を引き上げられるが、!importantと同じような問題がある。
しかし、このようなやり方を続けると本来.capitalに持たせたかった汎用性が失われ、html上で必要な分だけ.capitalを用意しなければならなくなる。最初の例ならdtも金色にしたいときはdt.capitalセレクタを記述しないといけない。そして何よりも、詳細度の引き上げであっちも上げなくちゃ、こっちも上げなくちゃという事態が発生する。言わば詳細度戦争である。

解決策3:すべてのスタイルをクラス指定のみにする

この方式を取り入れた一番有名な例が妖怪人間BEMである。要するに以下の様なコードにしてしまうのだ。

company_bem.html
<dl class="company_info">
  <dt class="company_info__dt">会社名</dt>
  <dd class="company_info__dd">株式会社ケイケイ(KK.inc)</dd>
  <dt class="company_info__dt">資本金</dt>
  <dd class="company_info__dd company_info__dd--capital">8,000万円</dd>
</dl>
company_bem.css
.company_info {
  display: grid;
  grid-template-columns: 5em auto;
}
.company_info__dt {
  font-weight: bold;
  color: #400;
}
.company_info__dt::after {
  content: ":"
}
.company_info__dd {
  color: #b00;
  margin: 0;
}
.company_info__dd--capital {
  color: gold;
  background: black;
}

これだったら詳細度が固定され、cssの管理が飛躍的にしやすくなる。
しかし、CSS本来の特性であるカスケードが有効活用できないのは残念だし、htmlコードのほうが長くなる。また、個人的にはclass属性の但し書き「コンテンツの期待するプレゼンテーションを記述する値よりもむしろ、コンテンツの性質を記述する値を使用するよう推奨される」(HTML Standard 日本語訳より)が無視されてしまうことが気になる3

解決策4:ユーティリティークラスを多用する

要するにBootstrapのような見た目の指定に特化したテンプレートを利用してしまおうという話。もしくは関数型CSS (atomic CSS, functional CSS)と言って、CSSを値指定に特化してしまおうという考え方もある。自分はBootStrapに関する知識がそれほどないので、自前でそれっぽいユーティリティークラスを用意する。

company_bem.html
<dl class="grid-dl">
  <dt class="bold after-colon color-400">会社名</dt>
  <dd class="color-darkRed m0">株式会社ケイケイ(KK.inc)</dd>
  <dt class="bold after-colon color-400">資本金</dt>
  <dd class="color-gold back_in_black m0">8,000万円</dd>
</dl>
company_bem.css
.grid-dl {
  display: grid;
  grid-template-columns: 5em auto;
}
.bold {
  font-weight: bold;
}
.color-400 {
  color: #400;
}
.after-colon::after {
  content: ":"
}
.color-darkRed {
  color: #b00;
}
.m0 {
  margin: 0;
}
.color-gold {
  color: gold;
}
.back_in_black {
  background: black;
}

この方法の優れている点は、見た目だけに注力できるので、デザインの調整がしやすくなることである。デザイナーにとっては一番扱いやすいだろう。Bootstrapは感覚的に見た目の良いサイトを構築できるようにできているので、筆者のような美的音痴にはうってつけだ。

しかし、筆者はあまりこのような手段を使いたくない。このような使い方はソースのセマンティック性を損ねているからだ。簡単に言えば、目の見えない人に「ここは金色文字ですよ」と言っても意味が無い。また、外面ばかり気にしていると、HTMLの書き方が雑になり、divの乱用や間違ったタグの使い方という悲劇が起こる。そうなるとARIAなどを使わなければならなくなる。本来ARIAは付加的なものなので、HTMLが意味を持って書かれていれば必要性は少ないはずだ

しかも、この手のユーティリティーは特定の画面サイズ、例えば横幅1366pxなどに特化したものになりがちなので、画面サイズがそれよりも小さければ容易に崩れる(大きくても、見れはするがスペースの有効活用がされないことが多い)。関数型CSSは特にこれらの問題に直面しやすい(さすがに企業のサイトではメディアクエリなどで対策しているはずだが)。Bootstrapはそこら辺を考慮してはいるが、結局想定サイズを何種類も用意してあるだけなので、横幅毎に(可能な限り)最適な選択肢を設定しなければならないわけだ。

総じて、ユーティリティークラスは現代のテーブルレイアウトと言ってよいと思う。現在の問題を解決するにはかなり強力だが、その本質は間違った使い方に基づいているからだ。

解決策5::where()を使う

長々と今までの解決策を語ってたが、ようやく本題。今までcssの記述と詳細度が一体のものとして扱われてきたが、それを打ち崩すセレクタが2つ登場した。:where():is()だ。
両者ともカッコ内に含まれたcssセレクタのいずれかと対応する要素にcssを当てていくが、:is()カッコ内の一番詳細度が高いセレクタの詳細度を採用するのに対して、:where()詳細度を無視する。これらのおかげで、詳細度の管理が格段に楽になる。

特に:where()の登場は今までのCSSの常識を覆す、いわゆるゲームチェンジャーだ。だって、構造を指定するだけのセレクタは全部where()にぶっこんでしまえば詳細度管理の手間が省けるのだから

company_where.css
.company_info {
  display: grid;
  grid-template-columns: 5em auto;
}
:where(.company_info) dt {
  font-weight: bold;
  color: #400;
}
:where(.company_info) dt::after {
  content: ":"
}
:where(.company_info) dd {
  color: #b00;
  margin: 0;
}
.capital {
  color: gold;
  background: black;
}

これで.company_infoの詳細度に悩まされることが無くなる。常に一番右以外のセレクタは:where()で囲んでいいのではないだろうか。

上記の例はすでにFireFox(確認済み)とSafari(すみません、未確認です)で製作者の意図したとおりに表示されるが、Chromeの場合でもバージョン88にアップデートすれば正しく表示されるはずである。

終わりに

そもそも、CSS1が策定された当時、ここまで大規模なWeb開発は想定されていなかったのではないかと思う。だから、詳細度という実装が簡単そうなシンプルなルールを考えたのではというのが筆者の持論。多分、右側から詳細度を比較していくルール4だったら、BEMが登場する理由は無かった。それはそれで問題が発生していたかもしれないが、ペーペーの筆者には思い当たらない。

CSS制定段階の設計ミスを挽回できる機会がようやく整ったことを喜びたい。来年1月からほぼすべての現行ブラウザで使えるようになる:where()セレクタのおかげでCSSは書きやすく、管理しやすいものになっているだろう。来年はIEの放逐が進み、デザイナーの負荷軽減がなされることが予想されるが、CSSの進化の恩恵は特に大きい。

改訂履歴

2021/1/20
タイトルを「:where()セレクタは詳細度戦争を終結させてくれるかもしれない」から変更
Chrome88がWhereセレクタに対応したことを追記

2021/01/22
backgound となっていた部分をbackground に修正。またつまらないミスを修正してしまった(CV.井上真樹夫)。


  1. この答えを引き出したのは筆者です(手前味噌)。なお、Chrome Platform Statusでは「Enabled by default」となっているが間違い。なぜ修正されないんだ… 

  2. なんでcolor指定がRGBとキーワード混在なのか、そもそもddに色を付けることがコードとして適切とは思えない。 

  3. まあ、ユーティリティクラスを使っている時点で、この但し書きを無視しているんだけれどもね。 

  4. 今回は.capitalddの比較をして、.capitalの方が大きいから優先される。もし等しい詳細度なら左側のセレクタを見て決めるというアルゴリズム。 

11
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3