CSSのメディアクエリー(Media Query)を使い、画面幅に基づいて要素の表示方法を変える方法はもうおなじみでしょう。エレメントクエリー(Element Query)はメディアクエリーと似ているものの、なんとビューポートではなくWebページ上の個々の要素にレスポンシブな条件を適用します。条件とは、たとえば、要素の幅、含まれる文字数、ユーザーのスクロール状況などで、要素に異なるスタイルルールを適用できるのです。
エレメントクエリーが必要とされる理由
最初に述べたように、エレメントクエリーはビューポートの幅と高さだけでなく、たくさんのプロパティに基づいて要素をスタイリングするのに役立ちます。ほかにもエレメントクエリーの使い勝手の良さが際立つ場合があります。
たとえば、すべての要素が完璧に調和した美しいレスポンシブレイアウトを作成したとします。そこへ既存の要素とともに、もう1カラム追加してほしいと頼まれます。こうなると、すべてのカラムで利用できるスペースが少なくなってしまいます。
1366px幅のビューポートに対して800px幅のカラムで完璧な比率となっていた画像とテキストは、同じビューポートに対して600px幅のカラムに収めると見栄えが悪くなるかもしれません。このような場合ビューポート幅はまったく変わっていないのに、サイドバーのスペースのために個々のカラム幅はさらに狭くなっています。こうした状況でメディアクエリーの代わりにエレメントクエリーを使うと、レイアウトの変更が必要になるたびにCSSを書き直さなくても良いので、時間が大幅に節約できます。
以下のデモでは、ビューポートが比較的小さい(500px未満)場合、画像幅を100%に設定して全体を単一のカラムに移動させます。しかし、ビューポートが比較的大きい場合、「Add Sidebar(サイドバーの追加)」ボタンをクリックするとメインカラムの幅は明らかに減少しますが、ビューポイント幅はまったく変わりません。結果として画像が小さくなりすぎます。
さて、ウィジェットやプラグインを作成していてメディアクエリーを使ってレスポンシブにするとします。ここでの課題は、ウィジェットが収まるコンテナの幅がビューポートと同じ場合も、ビューポートの1/4しかない場合もあるということです。
ウィジェットの幅がコンテナの幅によって決まる場合、ビューポートのサイズに従ってスタイリングしたのでは当然うまくいきません。ここで役立つのがエレメントクエリーなのです! エレメントクエリーはウィジェット自体の幅を使ってスタイリングするのでこうした状況でうまく機能します。
下の画像では単一のWebページ上に同じウィジェットが2つ配置されています。ビューポート幅は同じなのに、2つのウィジェットの幅は異なっています。ウィジェットの幅に基づいてスタイルルールが適用されているので、両方ともレイアウトにぴったり合っているのです。
エレメントクエリーを使うと、要素のレスポンシブ条件はページレイアウトに依存しなくなります。周囲の要素を気にせずにナビゲーションバーやテーブルといったコンポーネントを作成・スタイルリングできるのです。つまりWebサイトA用に作成された価格表をそのままWebサイトBで使えるということです。テンプレートの作成時、非常に役立ちます。
エレメントクエリーを使ってみよう
プロジェクトでエレメントクエリーを使うには、HTMLに専用のライブラリー「EQCSS.js」をインクルードする必要があります。ファイルをGitHubリポジトリからダウンロードしても、CDNjsでホストされているminified版ファイルに直接リンクしてもOKです。
EQCSSはIE9以上を含むすべてのモダンブラウザーに対応しています。IE8に対応させるにはプロジェクトにポリフィルのインクルードが必要です。ポリフィルはEQCSS自体よりも先にインクルードしなければなりません。
<!‐‐[if lt IE 9]>
<script src="https://cdnjs.cloudflare.com/ajax/libs/eqcss/1.4.0/EQCSS-polyfills.min.js"></script>
<![endif]‐‐>
<script src="https://cdnjs.cloudflare.com/ajax/libs/eqcss/1.4.0/EQCSS.min.js"></script>
必要なファイルのインクルード完了後、プロジェクトでEQCSSを使い始められます。2つの方法がありますが、そのうち簡単なのは通常のCSSで<style>か<link>タグ内にEQCSSをそのまま記述する方法です。別の方法としては次のようにカスタムタイプで<script>タグ内にEQCSSスタイルを分離してインクルードします。
<script type=text/eqcss>
/* Put your EQCSS here */
</script>
or
<script type="text/eqcss" src="path/to/styles.eqcss"></script>
デフォルトでスクリプトはコンテンツのロード後に、resize、scrollイベント時に全スタイルを実行・計算します。バージョン1.2以降、input、click、mouseup、mousemoveイベントもリスニングされるようになりました。scrollイベントについては、クエリーがbodyかhtmlのいずれかに適用される場合windowにアタッチされます。それ以外の場合、scrollイベントはクエリーで指定された要素にアタッチされます。
なにかほかのイベントに基づいてスタイルを再計算することが必要な場合は、EQCSS.apply()を呼び出せます。
エレメントクエリーを記述する
エレメントクエリーのシンタックスはメディアクエリーとよく似ています。たとえば個々のエレメントクエリーは@elementで始めて、少なくとも1つのスタイルを適用するセレクタが続きます。
@element {selector} and {condition} {
/* All your valid CSS */
}
デモの画像は、次のクエリーを使えばちょうどコンテナの幅いっぱいに収められます。
@element ".content" and (max-width: 480px) {
.content img {
width: 100%;
}
}
上のクエリーでは、コンテンツ幅が480px未満になり次第、含まれている画像がすべて幅100%に設定されます。ここで1つ問題になるのは、EQCSSによるスタイルの再計算がブラウザーのサイズ変更時か、clickまたはscrollイベントの発生時に限られることです。この場合ボタンの「click」イベント内でEQCSS.apply()を呼び出す必要があります。
こちらのデモでは、カラムの追加に画像がどのようにうまく適応するかを示します(要素が480px未満にならないときはこのページのウィンドウサイズを小さくする必要があるかもしれません)。
EQCSSが対応する条件は要素の幅や高さに限定されません。要素に含まれる文字数に基づくスタイリングもできます。思い浮かぶ例の1つは、SitePointトップページのカードベースのレイアウトです。「featured(おすすめ)」セクションの下に表示されるすべてのカードの幅と高さは同じですが、カードのタイトルの長さはさまざまです。タイトルが長すぎてカード内に収まらない場合、次のようにEQCSSを使ってフォントサイズを縮小できます。
@element ".card h2" and (min-characters: 50) {
$this {
font-size: 1em;
}
}
ここで.card h2の代わりに$thisが使われていることに気づいた人もいるでしょう。このようにしたのは.card h2を使うとすべてのカードの見出しのフォントサイズが変更されるのに対し、$thisを使うと文字数が指定の制限を超える見出しのフォントサイズだけが変更されるからです。$thisと同様のセレクタにはほかに$parent、$prev、$nextがあります。これらのセレクタはまとめて「meta-selectors(メタセレクタ)」と呼ばれます。
エレメントクエリーを使う
エレメントクエリーを試す前に、エレメントクエリーがソリューションとしてプロジェクトに向いているかどうか判断(さらに不適切な使用を避ける方法を理解)できるように、実際にどのように動くか知りたいと思うことでしょう。以下にどのように動いているかを説明します。
すべてのエレメントクエリーはループし、それぞれのクエリーがターゲットとする要素を見つけます。次いでその要素すべてをループし、属性(attribute)の形でそれぞれに固有の識別子を割り当てます。識別子のフォーマットはdata-eqcss-{element-query-index}-{matched-element-index}です。要素の親を識別するために、要素の識別子に-parentを追加して得られるデータ属性を親に追加します。同じ属性は上と下の「兄弟」にも追加されます。最後にそれぞれの要素とそのクエリーをmin-height、max-height、min-scroll-xなどすべての可能な条件に対応させて、適切なスタイルを適用します。
スタイルはドキュメントの<head>タグ内に追加されます。絞り込みによって、EQCSS.apply()が200ミリ秒(デフォルトのタイムアウト値)に1回を超えて呼び出されることは決してなくなります。resize、input、clickイベントとは異なり、scrollイベントはscroll要素のクエリーを使った要素上でのみリスニングされます。
さまざまな要素に対するスタイルはJavaScriptを使って計算されるため、パフォーマンスはスタイルが適用される要素の数によって決まります。要素が多すぎると、FirefoxでもEdgeでもはっきりした遅れが出ます。パフォーマンスは最新版のFirefoxで改善されましたが、将来もっと良くなる見込みがあります。
理想的には、プラグインで要素に対して1つか2つのEQCSSデータ属性が追加されるのが良いでしょう。注意して使わないと1つの要素に対して数十ものEQCSSが存在する羽目になり、結果としてマークアップがとても乱雑になるおそれがあります。マークアップが下の画像のようになるのは避けたいでしょう。
上のような属性は必ずしも手動でオリジナルのHTMLに追加する必要はないということを忘れないでください。属性はEQCSSの動作時に自動的に追加されるのです。
EQCSSのデバッグはメディアクエリーよりも少し面倒です。現時点でブラウザーが認識するのはメディアクエリーのみでエレメントクエリーは認識しません。ブラウザーはEQCSSが適用したスタイルを認識しているに過ぎないのです。
これまでに書いてきたすべての特徴を理解すると、メディアクエリーから完全にエレメントクエリーに乗り換えたいという気持ちに駆られるかもしれませんが、そうはしないでください。
第一に、メディアクエリーはJavaScriptでスタイルを計算するのでエレメントクエリーよりも動作が高速です。エレメントクエリーを使いすぎると(FirefoxとIEで)サイトのパフォーマンスがひどく低下しかねません。
ちなみに、EQCSSは、2015年にはFirefoxでスムーズに動いていました。ところが2016年のはじめにブラウザーにいくらか変更が加えられた結果、パフォーマンスが低下し、開発者はEQCSS.throttle()をライブラリーに追加してスタイルを再計算する頻度をコントロールしなければならなくなりました。
第二に、メディアクエリーの用途は画面上の要素のスタイリングだけではありません。ほかのメディアタイプや機能のスタイル設定にも使われます。一例として、メディアクエリーは印刷用のWebページに適用されるスタイルの記述にも必要です。
最後に
EQCSSは適切に使えばすばらしいツールです。メディアクエリーでは対応できない多くの場面で役立ちます。Webページ上にたくさんの要素がある場合、パフォーマンスが課題になることもありますが、シンタックスがメディアクエリーと似ているので習熟がさほど難しくないというメリットもあります。
将来的に、ブラウザーにはmax-width、min-height、max-heightといった条件によってより良いプラグインを作成できるResizeObserverのような機能や、ブラウザーに直接サポートを追加できるHoudiniといった機能が搭載されることでしょう。
EQCSSの開発者たちはこうした機能をすべて利用できるプラグインの作成に取り組んでいく一方、現在のEQCSSのサポートも続ける予定です。開発者たちは対応が望まれる特定のブラウザーにそれぞれ適合する、一群のプラグインの開発を目指しています。
※本記事はAdrian Sandu、Giulio Mainardi、Tom Hodginsが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:Writing Element Queries Today Using EQCSS)
[翻訳:新岡祐佳子/編集:Livit]