CSSコンテナクエリ:ユースケースと移行戦略

公開: 2022-03-10
クイックサマリー↬CSSコンテナクエリは、メディアクエリをターゲット要素自体に近づけ、事実上すべての特定のコンテナまたはレイアウトに適応できるようにします。 この記事では、CSSコンテナークエリの基本と、プログレッシブエンハンスメントまたはポリフィルを使用してそれらを今日使用する方法について説明します。

UI要素のメディアクエリを作成するときは、画面のサイズに応じてその要素のスタイルを常に説明します。 このアプローチは、ターゲット要素のメディアクエリの応答性がビューポートのサイズのみに依存する必要がある場合にうまく機能します。 次のレスポンシブページレイアウトの例を見てみましょう。

レスポンシブページレイアウトの例。左の画像は1280pxのビューポート幅のデスクトップレイアウトを示し、右の画像は568pxのビューポート幅のモバイルレイアウトを示しています。
レスポンシブページレイアウトの例。 左の画像は1280pxのビューポート幅のデスクトップレイアウトを示し、右の画像は568pxのビューポート幅のモバイルレイアウトを示しています。 (大プレビュー)

ただし、レスポンシブWebデザイン(RWD)は、ページレイアウトに限定されません。通常、個々のUIコンポーネントには、ビューポートのサイズに応じてスタイルを変更できるメディアクエリがあります。

レスポンシブ製品カードのコンポーネントの例。左の画像は720pxのビューポート幅のコンポーネントを示し、右の画像は568pxのビューポート幅のコンポーネントレイアウトを示しています。
レスポンシブ製品カードのコンポーネントの例。 左の画像は720pxのビューポート幅のコンポーネントを示し、右の画像は568pxのビューポート幅のコンポーネントレイアウトを示しています。 (大プレビュー)

前のステートメントの問題にすでに気付いているかもしれません。個々のUIコンポーネントのレイアウトは、多くの場合、ビューポートのサイズだけに依存しているわけではありません。 ページレイアウトはビューポートのサイズに密接に関連する要素であり、HTMLの最上位要素の1つですが、UIコンポーネントはさまざまなコンテキストやコンテナで使用できます。 考えてみれば、ビューポートは単なるコンテナであり、UIコンポーネントは、コンポーネントのサイズとレイアウトに影響を与えるスタイルを使用して、他のコンテナ内にネストできます。

上部の3列グリッドと下部のセクションリストに同じ製品カードUIコンポーネントを使用したページレイアウトの例。
上部の3列グリッドと下部のセクションリストに同じ製品カードUIコンポーネントを使用したページレイアウトの例。 (大プレビュー)

上部セクションと下部セクションの両方で同じ製品カードコンポーネントが使用されている場合でも、コンポーネントスタイルは、ビューポートの寸法だけでなく、コンテキストと、それが配置されているコンテナのCSSプロパティ(例のグリッドなど)にも依存します。

もちろん、CSSを構造化できるため、さまざまなコンテキストやコンテナーのスタイルバリエーションをサポートして、レイアウトの問題に手動で対処できます。 最悪のシナリオでは、このバリエーションにスタイルオーバーライドが追加され、コードの重複と特異性の問題が発生します。

 .product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }

ただし、これは適切な解決策というよりも、メディアクエリの制限に対する回避策です。 UI要素のメディアクエリを作成するとき、ターゲット要素の最小寸法がレイアウトが崩れない場合に、ブレークポイントの「魔法の」ビューポート値を見つけようとしています。 つまり、 「魔法の」ビューポートの寸法値を要素の寸法値にリンクしています。 この値は通常、ビューポートの寸法とは異なり、内部コンテナの寸法やレイアウトが変更されたときにバグが発生しやすくなります。

メディアクエリを要素のディメンションに確実にリンクできない例。さまざまなCSSプロパティが、コンテナ内の要素の寸法に影響を与える可能性があります。この例では、コンテナのパディングは2つの画像間で異なります。
メディアクエリを要素のディメンションに確実にリンクできない例。 さまざまなCSSプロパティが、コンテナ内の要素の寸法に影響を与える可能性があります。 この例では、コンテナのパディングは2つの画像間で異なります。 (大プレビュー)

次の例は、この正確な問題を示しています。レスポンシブ製品カード要素が実装されており、標準のユースケースでは見栄えがしますが、要素の寸法に影響するCSSプロパティを持つ別のコンテナに移動すると壊れているように見えます。 ユースケースを追加するたびに、CSSコードを追加する必要があります。これにより、コードの重複、コードの膨張、および保守が困難なコードが発生する可能性があります。

Adrian Beceによるペン[製品カード:さまざまなコンテナ](https://codepen.io/smashingmag/pen/eYvWVxz)を参照してください。

ペン製品カード:AdrianBeceによるさまざまなコンテナを参照してください。

これは、コンテナクエリが修正しようとする問題の1つです。 コンテナクエリは、ターゲット要素のディメンションに依存するクエリを使用して、既存のメディアクエリ機能を拡張します。 このアプローチを使用する主な利点は3つあります。

  • コンテナクエリスタイルは、ターゲット要素自体のディメンションに応じて適用されます。 UIコンポーネントは、特定のコンテキストまたはコンテナーに適応できます。
  • 開発者は、ビューポートメディアクエリを特定のコンテナまたは特定のコンテキスト内のUIコンポーネントのターゲットディメンションにリンクする「マジックナンバー」のビューポートディメンション値を探す必要はありません。
  • さまざまなコンテキストやユースケース用にCSSクラスやメディアクエリを追加する必要はありません。
「理想的なレスポンシブウェブサイトは、複数のコンテキストでサービスを提供するために再利用できる柔軟なモジュラーコンポーネントのシステムです。」

—「コンテナクエリ:違反に対してもう一度」Mat Marquis

コンテナクエリを深く掘り下げる前に、ブラウザのサポートを確認し、ブラウザで実験的な機能を有効にする方法を確認する必要があります。

ブラウザのサポート

コンテナクエリは実験的な機能であり、この記事の執筆時点でChromeCanaryバージョンで現在利用できます。 この記事のCodePenの例に従って実行する場合は、次の設定URLでコンテナークエリを有効にする必要があります。

 chrome://flags/#enable-container-queries
コンテナクエリ
(大プレビュー)

コンテナクエリをサポートしていないブラウザを使用している場合は、意図した動作例を示す画像がCodePenデモと一緒に提供されます。

ジャンプした後もっと! 以下を読み続けてください↓

コンテナクエリの操作

コンテナクエリは、通常のメディアクエリほど単純ではありません。 コンテナクエリを機能させるには、UI要素にCSSコードを1行追加する必要がありますが、それには理由があり、次に説明します。

封じ込めプロパティ

CSS containsプロパティcontain 、最新のブラウザーの大部分に追加されており、この記事の執筆時点では75%のブラウザーサポートがあります。 containプロパティは、主に、ページのどの部分(サブツリー)を独立として扱うことができ、ツリー内の他の要素への変更に影響を与えないかをブラウザーに示唆することにより、パフォーマンスを最適化するために使用されます。 そうすれば、単一の要素で変更が発生した場合、ブラウザはページ全体ではなく、その部分(サブツリー)のみを再レンダリングします。 containプロパティ値を使用して、使用する包含のタイプ( layoutsize 、またはpaint )を指定できます。

使用可能なオプションとユースケースをより詳細に概説するcontainプロパティに関する優れた記事がたくさんあるので、コンテナクエリに関連するプロパティのみに焦点を当てます。

最適化に使用されるCSSコンテンツプロパティは、コンテナクエリとどのような関係がありますか? コンテナクエリが機能するためには、ブラウザは、要素の子レイアウトに変更が発生したかどうか、そのコンポーネントのみを再レンダリングする必要があるかどうかを知る必要があります。 ブラウザは、コンポーネントがレンダリングされたとき、またはコンポーネントの寸法が変更されたときに、コンテナクエリのコードを一致するコンポーネントに適用することを認識します。

contain​にはlayout​ style​を使用しますが、変更が発生する軸についてブラウザに通知する追加の値も必要になります。

  • inline-size
    インライン軸上の封じ込め。 この値にはかなり多くのユースケースがあると予想されるため、最初に実装されています。
  • block-size
    ブロック軸上の封じ込め。 それはまだ開発中であり、現在利用できません。

containsプロパティの小さな欠点の1つは、レイアウト要素contain containの子にする必要があることです。つまり、ネストレベルを追加することになります。

 <section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
 .card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }

この値をより離れた親のようなsectionに追加せず、コンテナを影響を受ける要素にできるだけ近づけていることに注意してください。

「パフォーマンスとは、作業を回避し、実行する作業を可能な限り効率的にするための技術です。 多くの場合、それはブラウザに反対するのではなく、ブラウザを操作することです。」

—「レンダリングパフォーマンス」、Paul Lewis

そのため、変更についてブラウザに正しく通知する必要があります。 離れた親要素をcontainプロパティでラップすると、逆効果になり、ページのパフォーマンスに悪影響を与える可能性があります。 containsプロパティを誤用するという最悪のシナリオでcontain 、レイアウトが壊れて、ブラウザが正しくレンダリングしない場合があります。

コンテナクエリ

containsプロパティがカード要素ラッパーcontain追加された後、コンテナクエリを記述できます。 cardクラスの要素にcontainプロパティを追加したので、その子要素のいずれかをコンテナクエリに含めることができます。

通常のメディアクエリと同様に、 min-widthまたはmax-widthプロパティを使用してクエリを定義し、すべてのセレクターをブロック内にネストする必要があります。 ただし、コンテナクエリを定義するために@mediaの代わりに@containerキーワードを使用します。

 @container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }

card__wrapper要素とcard__image要素はどちらも、 containプロパティが定義されているcard要素の子です。 通常のメディアクエリをコンテナクエリに置き換え、ナローコンテナの追加のCSSクラスを削除し、コンテナクエリをサポートするブラウザでCodePenの例を実行すると、次の結果が得られます。

この例では、ビューポートのサイズを変更していませんが、サイズ変更CSSプロパティが適用されている<section>コンテナ要素自体を変更しています。コンポーネントは、コンテナの寸法に応じてレイアウトを自動的に切り替えます。
この例では、ビューポートのサイズを変更していませんが、サイズ変更CSSプロパティが適用されている<section>コンテナ要素自体を変更しています。 コンポーネントは、コンテナの寸法に応じてレイアウトを自動的に切り替えます。 (大プレビュー)

Adrian Beceによるペン[製品カード:コンテナクエリ](https://codepen.io/smashingmag/pen/PopmQLV)を参照してください。

ペン製品カード:AdrianBeceによるコンテナクエリを参照してください。

現在、コンテナクエリはChromeデベロッパーツールに表示されないため、コンテナクエリのデバッグが少し難しくなっていることに注意してください。 将来的には、適切なデバッグサポートがブラウザに追加される予定です。

コンテナクエリを使用すると、事実上すべてのコンテナとレイアウトに適応できる、より堅牢で再利用可能なUIコンポーネントを作成できることがわかります。 ただし、コンテナクエリに対する適切なブラウザのサポートは、この機能ではまだ遠いです。 プログレッシブエンハンスメントを使用してコンテナクエリを実装できるかどうか試してみましょう。

プログレッシブエンハンスメントとポリフィル

CSSクラスのバリエーションとメディアクエリにフォールバックを追加できるかどうかを見てみましょう。 @supportsルールでCSS機能クエリを使用して、使用可能なブラウザー機能を検出できます。 ただし、他のクエリをチェックすることはできないため、 contain: layout inline-size style値のチェックを追加する必要があります。 inline-sizeプロパティをサポートするブラウザは、コンテナクエリもサポートしていると想定する必要があります。

 /* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }

ただし、このアプローチでは、コンテナクエリとメディアクエリの両方で同じスタイルが適用されるため、スタイルが重複する可能性があります。 プログレッシブエンハンスメントを使用してコンテナクエリを実装する場合は、SASSなどのCSSプリプロセッサまたはPostCSSなどのポストプロセッサを使用して、コードブロックの重複を回避し、代わりにCSSミックスインまたは別のアプローチを使用することをお勧めします。

Adrian Beceによるペン[製品カード:プログレッシブエンハンスメントを備えたコンテナクエリ](https://codepen.io/smashingmag/pen/zYZwRXZ)を参照してください。

ペン製品カード:AdrianBeceによるプログレッシブエンハンスメントを備えたコンテナクエリを参照してください。

このコンテナクエリの仕様はまだ実験段階であるため、仕様または実装は将来のリリースで変更される可能性があることに注意することが重要です。

または、ポリフィルを使用して信頼性の高いフォールバックを提供することもできます。 強調したい2つのJavaScriptポリフィルがあります。これらは現在積極的に維持されており、必要なコンテナークエリ機能を提供しているようです。

  • ジョナサンニールによるcqfill
    CSSおよびPostCSS用のJavaScriptポリフィル
  • クリス・ガルシアによるreact-container-query
    Reactのカスタムフックとコンポーネント

メディアクエリからコンテナクエリへの移行

メディアクエリを使用する既存のプロジェクトにコンテナクエリを実装する場合は、HTMLおよびCSSコードをリファクタリングする必要があります。 これは、メディアクエリへの信頼できるフォールバックを提供しながら、コンテナクエリを追加する最も速くて簡単な方法であることがわかりました。 前のカードの例を見てみましょう。

 <section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
 .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }

まず、メディアクエリが適用されているルートHTML要素を、 containプロパティを持つ要素でラップします。

 <section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
 @supports (contain: inline-size) { .card { contain: layout inline-size style; } }

次に、メディアクエリを機能クエリでラップし、コンテナクエリを追加します。

 @supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }

この方法ではコードが肥大化し、コードが重複しますが、SASSまたはPostCSSを使用すると、開発コードの重複を回避できるため、CSSソースコードを維持できます。

コンテナクエリが適切なブラウザサポートを受け取ったら、 @supports not (contain: inline-size)コードブロックを削除して、コンテナクエリを排他的にサポートし続けることを検討してください。

Stephanie Ecklesは最近、さまざまな移行戦略をカバーするコンテナクエリに関するすばらしい記事を公開しました。 このトピックの詳細については、チェックすることをお勧めします。

ユースケースシナリオ

前の例からわかるように、コンテナクエリは、使用可能なコンテナスペースに依存し、さまざまなコンテキストで使用したり、ページ上のさまざまなコンテナに追加したりできるレイアウトの再利用性の高いコンポーネントに最適です。

その他の例には、次のものがあります(例には、コンテナークエリをサポートするブラウザーが必要です)。

  • カード、フォーム要素、バナーなどのモジュラーコンポーネント。
  • 適応可能なレイアウト
  • モバイルとデスクトップのさまざまな機能によるページ付け
  • CSSサイズ変更を使った楽しい実験

結論

仕様が実装され、ブラウザで広くサポートされると、コンテナクエリはゲームを変える機能になる可能性があります。 これにより、開発者はコンポーネントレベルでクエリを記述し、遠くてほとんど関連のないビューポートメディアクエリを使用する代わりに、関連するコンポーネントにクエリを近づけることができます。 これにより、さまざまなユースケース、レイアウト、およびコンテナに適応できる、より堅牢で再利用可能で保守可能なコンポーネントが実現します。

現状では、コンテナクエリはまだ初期の実験段階にあり、実装は変更される傾向があります。 今日からプロジェクトでコンテナクエリの使用を開始する場合は、機能検出によるプログレッシブエンハンスメントを使用してコンテナクエリを追加するか、JavaScriptポリフィルを使用する必要があります。 どちらの場合もコードにオーバーヘッドが発生するため、この初期段階でコンテナクエリを使用する場合は、機能が広くサポートされるようになったら、コードのリファクタリングを計画してください。

参考文献

  • David A. Herronによる「コンテナクエリ:クイックスタートガイド」
  • 「CSSコンテナクエリにこんにちは」AhmadShadeed
  • 「Chrome52でのCSSの封じ込め」、Paul Lewis
  • 「CSSContainプロパティを使用してブラウザを最適化するのを支援する」RachelAndrew