[CSS]メディアクエリとは異なる、レスポンシブ対応のモジュール式コンポーネントの実装に適したコンテナクエリ

最近ではデザインもJavaScriptもモジュール式のコンポーネント化が進んでおり、HTMLとCSSでも徐々に浸透している流れですが、完全に対応できているとは言えない状況です。例えば、メディア クエリはページレイアウトではうまく機能しますが、モジュール式のコンポーネントを実装するのは難しくなっています。

レスポンシブ対応のモジュール式コンポーネントを実装する際に適したコンテナ クエリの考え方を紹介します。

サイトのキャプチャ

Container Queries: Once More Unto the Breach
by Mat Marquis

下記は各ポイントを意訳したものです。
A List Apartと著者の許可を得て翻訳しています。

メディア クエリの問題点

複数のコンテナで使用されるモジュールをビューポートベースのメディア クエリでスタイルすることは、そのスタイルを使用する可能性があるすべてのコンテナにスコープすることを意味します。回りくどい言葉で表現しましたが、これはわたし達が実際にいつも行っていることだと思います。

理想的なレスポンシブ対応のWebサイトは柔軟なシステムで、複数の状況で役立つように再利用できるモジュール式のコンポーネントです。Brad FrostやDave OlsenによるPattern Labのようなパターンライブラリとツールを使って、Webサイトの基本的な部分をスタイルし、ページとは無関係にモジュールとして組み立てることができるべきです。そのモジュールは、どんなページレイアウトにでもコンテキストにでも利用することができます。しかし、現実はそう簡単ではありません。

説明のために、Whitworthのプロダクトページを構築するとしましょう。

プロダクトページのレイアウト

プロダクトページのレイアウト

デザインは非常にシンプルです。
ページ全体は大きなコンテナを占めるプロダクトのモジュールで構成されており、セカンダリ コンテナにはフィーチャーアイテムのモジュールがあります。

全体的なページのレイアウトは一つのブレークポイントで変化するだけで、フィーチャーアイテムのコンテナがサイドバーに移動します。レイアウトが変化する様子は、シンプルなデモページを用意しました。CSSも非常にシンプルです。

これを実装するには、2つの異なるコンテキストでモジュールを扱う必要があります。

  • フィーチャー コンテナが上部にある場合
  • フィーチャー コンテナがサイドバーにある場合

フィーチャー コンテナが上部にある場合は、プライマリとフィーチャーの2つのコンテナは同じ幅です。フィーチャー コンテナには.featuredというclassを与え、後でスタイルを調整できるようにします。

これでページの基本的なレイアウトができたので、個別のモジュールのレイアウトに集中することができます。まずは、利用可能なスペースのそれぞれに適した垂直と水平のレイアウトを用意します。

垂直と水平のレイアウト

垂直と水平のレイアウト

垂直レイアウトは、CSSではそれほど手間がかかりません。マークアップでは、画像サイズの上限をmax-widthで定義し、サイズを超えた場合には画像を中央に配置するだけです。

水平レイアウトのスタイルもそれほど難しくありません。今のところはプライマリ コンテナに使用することだけを考慮しているので、スタイルするために.featuredは使用しません。800px以上の場合は垂直レイアウトに戻したいので、水平レイアウトは400pxから799pxで適用されます。

デモページでこれらのスタイルがどのように機能するかを説明します。
ある程度まではうまく機能します。しかしフィーチャー コンテナのモジュールは1つしかないので、中間のブレークポイントでは期待通りに機能しません。799px以下にすると、フィーチャー コンテナの幅とプライマリ コンテナの幅が異なります。

デモページのキャプチャ

799px以下だと幅が揃わず、うまく機能していない

うまく機能していないので、修正します。
プライマリ コンテナ内のモジュールに対して新しいメディアクエリを作成するだけです。そして、すべてのスタイルをそのセカンダリ コンテナ要素に付けた.featuredから始まるclassに定義します。

デモページにあるように、この解決方法はうまく機能します。しかし、コード的には煩雑です。一言で言うと、CSSを書くためのDRYなアプローチではありません。

ここにあるような単純なデザインの場合は、まだ耐えられますが、ディテールを調整する際にはさらに悪化する可能性があります。
例えば、垂直レイアウトでは「Add to Cart」ボタンがモジュールの右側に表示されます。しかし、799px以下のブレークポイントでの3カラムのレイアウトに切り替わると、十分なスペースがありません。

「Add to Cart」と「残数」モジュール

「Add to Cart」と「残数」モジュールに注目

スタイルを調整すると、「Add to Cart」と「残数」モジュールがこれらのサイズを下回った場合、右のスペースにフロートするのではなく、左に沿って並んでしまいます。これは期待通りではありません。

これら2つの要素の左揃えと右揃えを切り替えるスタイル自体は、単純です。text-alignとfloatプロパティを使用するだけです。ただし、この単純なスタイルを制御するメディア クエリについては、問題点がいくつかあります。

垂直方向の配置を処理する既存の両方のブレークポイントに、2つのブレークポイントを追加する必要があります。これらのスタイルはfeaturedモジュールにも影響を与えるため、スコープのclassを使用して、一致するメディアクエリでこれらの新しいスタイルを上書きする必要があります。featuredモジュールは、横に3つ並んだモジュールよりもスペースがあるため、その場合は「Add to Cart」ボタンを右に移動することができます。

しかし、これだけではありません。これらのスタイルは将来性のあるものではありません。モジュールを新しいコンテキストに追加する必要がある場合は、新しいスコープのclassと新しいメディア クエリを使用して、すべてのスタイルをもう一度コピーして貼り付ける必要があります。ページレイアウトのサイズが多少変化した場合(paddingやmargin、レイアウト要素に対するサイズベースのスタイル)、これらたくさんのメディア クエリをすべて調整する必要があります。そして、ブレークポイントが意味をなす新しいビューポートのサイズを見つける必要があります。これはもうレイアウトから完全に切り離されています。

このレイアウトは実際のプロジェクトで遭遇するかもしれない複雑なレベルではないかもしれません。しかし、この時点で既にメンテナンスするのが困難なスタイルシートだと言えます。

エレメント クエリとは

このような複雑さはすべて、ビューポートに依存してスタイルを定義するのが原因です。メディア クエリはページレイアウトではうまく機能しますが、モジュール式のコンポーネントでは役に立ちません。わたし達が本当に必要としているのは、ビューポートのサイズではなく、それらが占めるスペースに基づいてページで使用するコンポーネントをスタイルする機能です。
参考: Media Queries are a Hack

エレメント クエリとは、まさにそれをもたらす構文を想定しています。例えば、ビューポートが40emを超える場合にのみ適用されるスタイルを記述する際、わたし達は下記のようなメディア クエリを書くことに慣れています。

上記のコードの代わりに、クエリ自体が特定の要素にスコープされ、その要素が40emより大きい場合にのみ適用されるスタイルが設定される構文を想像してみてください。

このコードを使用すると、さきほどのデモで書いたコードのほとんどすべてが変わります。

レイアウトの大きな変更に、メディア クエリを使用することもできます。ここでは線形レイアウトから、より大きな基本要素とサイドバーまで移動させます。これをビューポートのサイズに基づいて考えるのは理にかなっています。モジュールを自分で構築するようになると、この新しい構文によって、1度だけ必要となる可能性があるすべてのスタイルを作成できるようになります。

このコードが、モジュール式のレイアウトで必要とするスタイルです。デモページでは、JavaScriptを使用してこの理論上の構文を実際に機能させています。

デモのキャプチャ

モジュールが425pxより大きい場合は水平レイアウトを使用し、小さい場合は垂直レイアウトを使用します。スタイルはモジュールが占めるコンテナに100%依存しており、水平レイアウトに十分なスペースがある場合は、それに適したスタイルが適用されます。

各モジュールがコンテナの半分を占めた状態で、3カラムを2カラムに変更した場合、またはモジュールに予測不可能なコンテキストが加えられた場合、それらのスタイルを変更する必要はありません。このようなスタンドアロンのモジュールからコンテキスト依存関係を削除した場合、パターンライブラリがどれほど有用になるかを想像してみてください。

何百行ものCSSを修正せずに、あるいはCSSをまったく修正せずに、ページから任意のコンポーネントを取得してページ内の任意のコンテナに移動できます。

「Add to Cart」ボタンや残数の位置など、ディテールを微調整する場合でも、複雑にはなりません。ビューポートベースのブレークポイントと一致するようにモジュールのブレークポイントをスコープに使用した際に、懸念事項が1つだけあります。

価格と「Add to Cart」ボタンを並べて表示するにはモジュールが小さすぎませんか?

実際に試したところ、モジュールが320pxよりも大きい場合、両方の要素にとって十分なスペースがあることが分かりました。そこが、モジュールベースのブレークポイントを設定するポイントです。

何十行もの冗長なCSSが、たった9行のCSSになりました。さらに重要なことは、ページの他のスタイルが変更されても問題ありません。このエレメント クエリを使用したデモページをデベロッパーツールでスタイルをいろいろ試してみてください。

サイトのキャプチャ

デモページ

本番サイトのレベルではありませんが、エレメント クエリのコンセプトを理解するには最適です。このコードを使用すれば、数行のCSSでこのWhitworthのプロダクトページを完成させることができます。

しかし、エレメント クエリを使用することはできません。少なくとも、この実装は考えていた方法ではありません。picture要素の仕様の最初のバージョンと同様に、エレメント クエリは死んで永遠に埋められています。しかし、レスポンシブ画像のように、その問題を解決しようとしているのではありません。解決すべき明確な問題があることが分かりました。

エレメント クエリがダメな理由

このコンセプトは制作者に押し売りするわけではありませんが、どちらも画像がレスポンシブに対応していません。しかし実装のディテールに入ると、状況は変わります。

RICGで最初のステップは、エレメント クエリ用のユースケース(CQ Use Cases)を作成し、問題を提起することです。解決策を提示するのではなく、解決する明確な問題を提供するだけです。Standardizing Responsive Imagesよりもずっと焦点を絞っています。

この言葉が広まるにつれ、多くの人がこの問題について考え始めました。提案された仕様に慣れるまでは、多くの人が同じ結論を導きました。エレメント クエリは機能しません。

その結論はElement Queriesで公開されています。要約すると、CSSを制御するためにCSSを使用すると、スタイルを無限ループすることになってしまうからです。

もしエレメント クエリを使用して自分自身のスタイルを変更するように要素に定義できる場合、そのエレメント クエリを使用して要素の幅をエレメント クエリが適用されなくなった幅に変更するとどうなるでしょうか?
コードにすると、こんな感じです。

これではクエリが一致しないため、新しい幅は適用されません。新しい幅は適用されないため、エレメント クエリは再び一致させ、新しい幅が適用されます。そしてまた、クエリは一致しなくなり、新しい幅は適用されません(この繰り返しは無限ループです)。

ほんの数行のCSSでこのパラドックスを証明しましたが、ブラウザがこれを処理する方法はありません。ブラウザがこの状況になった際、エレメント クエリを完全に捨てるべきでしょうか。

ブラウザの各ベンダーからはこの点について特に魅力的な提案は何もありません。このようなエラーの可能性があるものに日の目を見させることは決してありません。ゲームオーバーです。

エレメント クエリが不可能であることが分かりました。

しかし、私の話を最後まで聞いてください。
エレメント クエリは終了しましたが、問題はそのままです。解決方法はどこにも出ていません。この問題は解決される必要があります、そして今わたし達はそれを解決できる方法を知っています。
エレメントすなわち、要素とは自身のプロパティに基づいてスタイルを変更することはできない、ということです。

コンテナ クエリとは

この問題を解決するために、見直す必要があります。要素はそれ自身を再スタイルすることができないので、その制約を仕様に組み入れることができます。そして、要素に紐付けられたクエリは、その要素の子要素のスタイルにのみ影響を与えます。

新しい知識を得たことで、エレメント クエリはコンテナ クエリに改良されました。

ブラウザがこれらの要素をどのように扱うかに関しては、動作上の変更をいくつか意味するかもしれません。例えばブラウザでは、クエリが紐付いている要素をその子要素のサイズの影響を受けないようにすることはできません。また、無限ループを防止するために、クエリが紐付いている要素にはブラウザレベルの暗黙的な上書きのスタイルが使用される可能性があります。

では、どうやってここから進めていけばよいのでしょうか。コンテナ クエリはエレメント クエリよりももっと綿密に精査されてきましたが、話は簡単です。この再帰問題は大きな問題ですが、それでも解決方法が具体化し始めたときに遭遇する可能性がある潜在的な問題の表面を傷つけているだけです。

このパターンを試すことで、さらに進みます。まだ存在しない構文を扱うときには、これを実行するのが最も簡単な方法ではありません。Scott Jehl氏のPicturefillの最初のバージョンが、何年も前に出てきたわけです。実際にはpicture要素のマークアップパターンに似たものを使用せずに、書き始めたばかりの仕様を理解することができませんでした。RICGはWhitworthのプロダクトページと同じやり方を使って、Container Query demosのリポジトリを公開しました。これで、この構文を試してみて、この種のCSSパターンを実際に使用するときに遭遇する問題、つまり何かを構築することによってのみ発見できる問題を見つけることができます。

リポジトリのクローンを作成し、demo-templateディレクトリを/demosにコピーし、デモに名前を付け、コンテナクエリを試してみてください。どうであるか、良いか悪いかに関わらず、必ず知らせてください。

コンテナ クエリに取り組もう

コンテナ クエリの仕様はContainer Queriesにまとめられています。ただし、ユースケースと要件のドキュメントは掲載されていません。

その答えは、まさしくこの仕様かもしれないし、全く異る別の何かかもしれません。しかし、RICGはWeb標準における意思決定者ではありません。そして、それを目指すものでもありません。デザイナーやデベロッパーが、日常業務に影響を与えるスタンダードな方法を提供するための集合場所になりたいだけです。

やるべきことはまだたくさんあるでしょう。それを実現するには、あなた達全員が必要です。何千ものデザイナーやデベロッパーの声が貢献し、その助けを借りて、一緒に答えが見つかるまで時間がかかるだけです。

あなたの語彙から「エレメント クエリ」というフレーズを永遠に消してください。将来どのような名前や形式になるかに関係なく、コンテナ クエリに取り組みましょう。

訳者注:
コンテナ クエリは2019年現在、CSSだけでは実装できません。EQCSSなどのJavaScriptが必要です。
参考: 要素の幅や高さや数に基づいてスタイルを定義できるスクリプト -EQCSS

sponsors

top of page

©2019 coliss