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を追加する場合を考える。
<dl class="company_info">
<dt>会社名</dt>
<dd>株式会社ケイケイ(KK.inc)</dd>
<dt>資本金</dt>
<dd class="capital">8,000万円</dd>
</dl>
.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 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である。要するに以下の様なコードにしてしまうのだ。
<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_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に関する知識がそれほどないので、自前でそれっぽいユーティリティークラスを用意する。
<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>
.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_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.井上真樹夫)。
-
この答えを引き出したのは筆者です(手前味噌)。なお、Chrome Platform Statusでは「Enabled by default」となっているが間違い。なぜ修正されないんだ… ↩
-
なんで
color
指定がRGBとキーワード混在なのか、そもそもdd
に色を付けることがコードとして適切とは思えない。 ↩ -
まあ、ユーティリティクラスを使っている時点で、この但し書きを無視しているんだけれどもね。 ↩
-
今回は
.capital
とdd
の比較をして、.capital
の方が大きいから優先される。もし等しい詳細度なら左側のセレクタを見て決めるというアルゴリズム。 ↩