Responsive Design On Steroids: CSS Container Queries
記事は上記記事を意訳したものです。
※当ブログでの翻訳記事は元サイト様に許可を得て掲載しています。
待望のレスポンシブデザインの機能であるコンテナクエリーが、ついに安定したバージョンとなり、期待通りの威力を発揮しています。本記事では、従来のメディアクエリーの限界を探り、コンテナクエリーがどのような優雅な解決策を提供するかを紹介します。さらに、コンテナクエリーの仕組みを掘り下げ、実際にコンテナクエリーを使ってWebページのサイドバーをレスポンシブにするハンズオン体験を提供します。
- 対象要素ベースのクエリー
- スコープ付きスタイル
- コンポーネントベースのデザイン
- 可変ベースのレスポンシブデザイン
- コンテナクエリーの仕組み
- レイアウトのスタイリングデモ
- Webページの作成
- デザイン問題の解決
- DevToolsでのコンテナクエリーのデバッグ
- ブラウザサポート
- 終わりに
Webデザインの世界では、開発者たちはメディアクエリーのブレークポイントを使って、Webページをシームレスでユーザー中心のものにするよう努力を重ねてきました。
メディアクエリーは、レスポンシブWebデザインの礎となる機能で、ユーザーのデバイスやビューポートの特性に基づいて、スタイルやレイアウトルールをWebページに適用することができます。対応する特性には、画面の幅や高さ、デバイスの向き、さらにはコンテンツの表示方法(印刷やスクリーンなど)も含まれます。
一方、コンテナクエリーはWebページのレイアウトデザインへのアプローチを革新的に変えるものです。
メディアクエリーと同様に、コンテナクエリーもレスポンシブなブレークポイントとして機能し、Webページ内の利用可能なスペースに基づいてレイアウトやコンテンツの順序を決定します。しかし、コンテナクエリーには大きな利点があります。ブラウザの全画面ビューポイントではなく、個々の要素に割り当てられたスペースをもとにブレークポイントを決めることができるのです。
さらに、コンテナクエリーを使えば、通常は親要素になる別の要素の利用可能なスペースに合わせて、要素のブレークポイントを最適なレイアウトに調整できます。背景や枠線、ボタン、フォント、レイアウトなど、あらゆるものを動的に変更することができます。
コンテナクエリーの優秀さを十分に理解するため、実例を見てみましょう。
まず、新しいhtmlファイル containerVsMedia.html を作成します。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Media vs Container Queries</title> <style> /* Media Query Styles */ @media screen and (max-width: 600px) { .media-box { background-color: lightblue; width: 100px; height: 100px; } } /* Container Query Styles */ .container { width: 50%; border: 1px solid #ccc; padding: 10px; container-type: inline-size; } .container-query-box { background-color: lightcoral; width: 50%; height: 100px; } /* Container Query */ @container (min-width: 300px) { .container-query-box { background-color: lightgreen; } } </style> </head> <body> <h2>Media Query Example</h2> <p>Box with media query:</p> <div class="media-box"></div> <h2>Container Query Example</h2> <div class="container"> <p>Box with container query:</p> <div class="container-query-box"></div> </div> </body> </html>
説明していきます。メディアクエリーを使用する要素と、コンテナクエリーを使用する要素の2つがあります。
まずメディアクエリーのボックスから見ていきましょう。ビューポートの幅が600px以下の場合、.media-boxの背景色がライトブルーに変わります。
次に、コンテナクエリーのボックスについてです。container div要素はビューポートの幅の50%を占め、その中の .container-query-box の背景色はライトコーラルになっています。
しかし、コンテナクエリーが適用されており、container div要素の幅が300px以上になると、.container-query-box の背景色がライトグリーンに変わり、300px未満に縮小するとコーラルに戻ります。
つまり、コンテナクエリーのボックス例では、ビューポートの変化ではなく、親要素の変化に応じて子要素の動作をコントロールできるのです。
下の画像では、ビューポート全体が変わるとメディアクエリーのボックスが非表示になりますが、コンテナクエリーのボックスは親要素の変化に応じて変わります。
構文については後ほど説明しますが、この例は多少単純化されすぎているかもしれません。しかし、containerVsMedia.htmlでコンテナクエリーを使うメリットは以下のとおりです。
対象要素ベースのクエリー
containerVsMedia.htmlのコンテナクエリーでは、.container-query-box要素を特定のコンテナ内でターゲットにしています。つまり、コンテナの寸法に基づいてスタイルを適用できるのです。
メディアクエリーでは通常、ビューポート全体に反応しますが、コンテナクエリーを使えば、要素の特定のコンテキストに基づいてデザイン決定ができます。これは、要素を親コンテナに応答させたい場合に特に便利です。
スコープ付きスタイル
コンテナクエリーのセクションでは、.container-query-boxクラスの要素の背景色が、最小幅300pxのコンテナ内にある場合にライトグリーンに変更されます。この変更はこの特定のコンテナ内に限定されます。
コンテナクエリーを使えば、スコープ付きのスタイルを実現できます。つまり、レイアウトの一部で変更を加えても、ページの残りの部分には影響しません。一方のメディアクエリーでは、変更がグローバルに適用されることが多いのと対照的です。
コンポーネントベースのデザイン
コンテナクエリーを使えば、.container要素とその中身を自己完結型のコンポーネントとしてレスポンシブな振る舞いを持たせることができます。
これはモジュール化されており、複雑なレイアウトの構築と保守がより簡単になります。一方で、メディアクエリーを使うとしばしば要素間の相互依存が生じ、保守性が低下する可能性があります。
可変ベースのレスポンシブデザイン
containerVsMedia.htmlでは明示的に示されていませんが、コンテナクエリーを使えば、固定幅のブレークポイントに加えて、コンテナ内の可変条件に対してもスタイルを変更できます。
パーセント値や特定の寸法、他のコンテナプロパティに基づいてスタイルを調整することができます。この可変ベースのアプローチにより、コンテナサイズの変化に対する要素の応答性をより柔軟に設定できます。従来のメディアクエリーではこれほど簡単には実現できません。
コンテナクエリーの仕組み
コンテナクエリーには素晴らしい機能があります。各コンポーネントが自身のレスポンシブな振る舞いを制御できるのです。つまり、コンテナクエリーはコンポーネントベースのアーキテクチャや環境と無理なく統合できます。コンポーネントレベルでのレスポンシブ性を実現するのが格段に簡単になったのです。
Webページのレイアウトとコンポーネントのレイアウトを完全に一致させる必要がなくなりました。各コンポーネントが親要素内の利用可能なスペースに合わせて自動的に調整できるからです。
すばらしいですね。さて、仕組みをさらに掘り下げていきましょう。
CSS コンテインメント
コンテナクエリーでは、Webページのレイアウトとコンポーネントのレイアウトがぴったり一致する必要はなくなりました。これにより、インターフェースの柔軟性が高まります。
従来はこのような柔軟性を実現するためにスクリプトを大量に使う必要があり、エラーも発生しがちでした。しかしCSSコンテインメントという機能が導入されたおかげで、これがCSSの本質的な部分となりました。
コンテナクエリーを使うための第一歩は、親要素にコンテインメントを適用することです。コンテインメントには、要素またはグループの要素がページの残りの部分とどのように相互作用するかを制御できる一連の機能が含まれています。
CSSコンテインメントにより、DOMのサブツリーをページの残りの部分から確実に分離できます。その結果、ブラウザのレンダリングパフォーマンスが向上し、コンテナクエリー用のクエリーを分離できるようになります。
コンテインメントを使えば、ブラウザにページのどの部分がコンテンツの独立したセットをカプセル化しているかを通知できます。そうすれば、ブラウザはそのサブツリー以外のコンテキストを考慮せずにそのコンテンツを分析できるようになります。つまり、サブツリーに焦点を合わせることができるのです。
コンテンツのセクションや独立したサブツリーを特定することで、ブラウザはレンダリングの決定を最適化できるようになり、結果としてページのレンダリングパフォーマンスが向上します。
コンテナクエリーの構文
CSSコンテインメントには、サイズ、レイアウト、スタイル、ペイントの4つのカテゴリーがあります。それぞれが特定の目的を果たします。
レイアウトコンテインメント: このカテゴリーでは、要素とその子孫のレイアウトを制御します。要素のサイズと位置がレイアウト内でどのように決定されるかに影響します。
ペイントコンテインメント: ペイントコンテインメントは、要素とその子孫のレンダリングを管理します。要素の描画と再描画の方法に影響します。
スタイルコンテインメント: スタイルコンテインメントでは、要素とその子孫のスタイルを制御します。要素のスタイルがどのように計算され、何に影響を受けるかを決定します。
サイズコンテインメント: サイズコンテインメントは、コンテインされた要素が親要素のサイズに影響を与えないようにします。
これらのコンテインメントタイプは個別に、または組み合わせて使用でき、特定の効果を実現できます。コンテナクエリーを実装するには、サイズ、レイアウト、スタイルのコンテインメントをすべて同時に設定する必要があります。
注: この見出しの下にあるすべてのコード例は、説明のためだけに提供されており、結果を示したり達成したりするものではありません。
.card-containerクラスのコンテナ要素にコンテナクエリーを使ってスタイルを適用したい場合、以下のようなコード例が示されています。
.card-container { contain: style layout inline-size; width: 100%; }
.card-containerにCSSコンテインメントを有効にするには、style、layout、そしてsize(inline-sizeで表される)を宣言する必要があります。inline-sizeは、コンテナのサイズが幅に応答するが高さには応答しないことを意味します。
コンテナのサイズを幅と高さの両方に応答させたい場合は、inline-sizeをsizeに置き換えるだけです。
.card-container { contain: style layout size; width: 100%; }
コンテナクエリーの設定プロセスをさらに簡素化するために、構文を短縮することもできます。
.card-container{ container-type: inline-size; width: 100% }
この構文では、styleとlayoutが暗黙的に定義され、サイズコンテインメントのオプションだけ選択できます。
コンテインメントを宣言するためのさらに短い方法もあり、コンテナに名前を付けることもできます。
.card-container { container: inline-size / the-container; width: 100%; }
the-containerはコンテナの名前で、後でコード内の別の要素にコンテインメントを適用する際に役立ちます。オプションではありますが、特に多層コンテナクエリーを扱う場合は、簡単に参照できるので便利です。
.card-container内の子要素にスタイルを適用したい場合は、以下の構文を使用します。
.card-container { container-type: inline-size; width: 100%; } @container (max-width: 250px) { .child-container { display: flex; align-items: center; gap: 2rem; } }
ここで重要な役割を果たすのが @container メディア機能です。
重要なのは、この動作が内在的なもので、.card-containerのサイズに基づいており、ブラウザの全体的なビューポートには依存していないことです。
.card-containerの幅が250px未満に縮小すると、そのコンテナ内の .child-containerに対してflexの方向やその他のflexプロパティが適用されるのです。
コンテナクエリーユニット
コンテナクエリーにはさらに注目すべき機能があります。コンテナクエリーベースのユニット値です。
UI要素のサイズ決めや間隔調整に一般的に使われるvhやvwといったビューポート相対のユニットがありますが、これら新しいコンテナクエリーユニットでは、コンテナの幅と高さに基づいてサイズを定義することができます。これらのユニットには"cq"というプレフィックスが付きます。ちょうどvhやvwにvがついているのと同様です。
cqw - クエリコンテナの幅の1%: このユニットは、使用されているコンテナの幅の1%に相当する長さを表します。コンテナの幅が1000ピクセルの場合、cqw(1)は10ピクセル(1000の1%)になります。
cqh - クエリコンテナの高さの1%: cqwと同様に、cqhはコンテナの高さの1%に相当する長さを表します。コンテナの高さが800ピクセルの場合、cqh(2)は16ピクセル(800の2%)になります。
- cqi - クエリコンテナのインラインサイズの1%: インラインサイズとは、水平の書字モードでは幅、垂直の書字モードでは高さに相当する寸法のことです。つまりcqiはこのインラインサイズの1%を表します。
- cqb - クエリコンテナのブロックサイズの1%: ブロックサイズはインラインサイズの反対の概念です。水平の書字モードでは高さ、垂直の書字モードでは幅になります。つまりcqbはこのブロックサイズの1%を表し、異なる書字モードでも適応できます。
- cqmin - cqiとcqbの小さい方の値: cqiとcqbのうち小さい方の値を表します。cqiが20ピクセル、cqbが15ピクセルの場合、cqminは15ピクセルになります。
- cqmax - cqiとcqbの大きい方の値: cqiとcqbのうち大きい方の値を表します。cqiが20ピクセル、cqbが15ピクセルの場合、cqmaxは20ピクセルになります。
レイアウトのスタイリングデモ
コンテナクエリーの実用性を効果的に示すために、開発者がよく直面するリアルな状況について確認しましょう。テキストセクションとサイドバーを含む簡単なWebページレイアウトを想定します。
複数のコンポーネントを含むレイアウトを作成する際、各コンポーネントに個別の振る舞いを持たせることは難しい課題です。たとえば、メインコンテンツとサイドバーのレイアウトを区別することは大変な作業かもしれません。
このような場合にこそ、コンテナクエリーが役立ちます。デフォルトのブラウザのレスポンシブ性やメディアクエリーを使うよりも、コンポーネントの振る舞いをカスタマイズしやすくなります。
デモは2つのフェーズで進めていきます。まず簡単にWebページを作成し、次にコンテナクエリーが解決策を単純化できる実際の問題を特定します。
Webページの作成
デモでは、コンテンツページとサイドバーの2つの主要なディビジョンがあります。記事を簡潔にするため、Webページの構造とスタイリングを提供し、簡単に説明します。
2つのファイルを作成します。HTMLファイルとCSSファイルです。
/containerQuery.html
/containerQuery.css
containerQuery.htmlにHTMLの構造を作成します。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Container Page</title> <link rel="stylesheet" href="containerQuery.css" /> </head> <body> <main> <article> <h1 class="page-title">Open Replay Blog</h1> <p> Lorem ipsum, dolor sit amet consectetur adipisicing elit. Similique doloribus doloremque tempora deleniti eveniet numquam. </p> <p> Incidunt esse quia quos? Cumque minima aliquid ut placeat nobis, quisquam eum omnis neque molestias. </p> <p> Aperiam temporibus autem ullam. Totam nesciunt quis blanditiis quas deserunt aperiam maxime debitis beatae tenetur! </p> <div class="call-to-action"> <h2 class="section-title">This is an insightful blog!</h2> <p>Read about the latest tech from the best developers.</p> <button class="button">Read more</button> </div> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet mollitia laboriosam inventore in voluptas fuga perspiciatis, cumque adipisci assumenda? Veritatis. </p> <p> Magni nostrum animi modi similique voluptatibus, tenetur aliquam dignissimos voluptates et totam? Veritatis doloribus, harum obcaecati voluptas quam dolores id. </p> <p> Quo quia quod error, accusantium debitis voluptatibus, cupiditate delectus aspernatur dignissimos sapiente voluptate incidunt beatae natus deserunt officia provident veniam. </p> <p> Officia architecto, praesentium reprehenderit doloribus cumque blanditiis. Veritatis error hic modi ad dicta. Dicta, aspernatur dolores vero itaque omnis eum. </p> <p> Sed, vitae, dolore atque deleniti quam ex expedita quae ducimus eos quis, voluptates et assumenda autem magnam molestias ullam obcaecati. </p> </article> <aside> <div class="call-to-action"> <h2 class="section-title">This is an insightful blog!</h2> <p>Read about the latest tech from the best developers.</p> <button class="button">Read more</button> </div> </aside> </main> </body> </html>
構造は、見出しやテキストのコンポーネントを含む article 要素のレイアウトと、コールトゥアクションの div を含む aside 要素のサイドバーで構成されています。
HTMLのプレーンな出力は以下のようになります。
次に containerQuery.css でスタイリングを行います。
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } main { max-width: 85em; margin-inline: auto; display: grid; align-items: start; gap: 2rem; } @media (min-width: 50em) { main { grid-template-columns: 1fr 12rem; } } aside { background: var(--beige); } .call-to-action { display: grid; gap: 1rem; background: var(--beige); padding: 2rem; } @media (min-width: 650px) { .call-to-action { grid-template-columns: 1fr min(10rem, 1fr); } .call-to-action > :not(.button) { grid-column: 1 / 2; } .call-to-action > .button { grid-row: 1 / 3; grid-column: 2 / 3; align-self: center; justify-self: end; } } /* general styling */ :root { --beige: #f2ffe9; --green-500: #557c55; --green-300: #a6cf98; } * { box-sizing: border-box; margin: 0; } body { font-family: system-ui; font-size: 1.125rem; line-height: 1.6; padding: 2rem; } h1, h2, h3 { font-weight: 900; color: var(--green-500); line-height: 1.1; } .page-title { margin-block-end: 2em; } .flow > :where(:not(:first-child)) { margin-top: 1em; } .button { cursor: pointer; border: 0; text-decoration: none; background: transparent; padding: 0.75em 1.5em; border-radius: 100vw; background: var(--green-300); font-size: 1rem; text-transform: uppercase; font-weight: 700; color: var(--beige); line-height: 1; }
これでページの見栄えが良くなります。
デモページのスタイル付けが完了し、以下のように見栄えが良くなりました。
デザイン問題の解決
レスポンシブデザインの実現は、新しいブラウザバージョンの継続的な改善により簡単になっています。しかし、モダンWebではコンポーネントベースの構造に依存しており、過去10年間の万能アプローチから離れています。そのような状況では、従来のメディアクエリーでは不十分な場合があり、コンテナクエリーのような新しい技術が最適な解決策となります。そのような例を見ていきましょう。
containerQuery.htmlでは、.call-to-actionクラスのdivがarticle要素とaside要素の両方で使用されています。
.call-to-actionコンポーネントは同じクラスを使用しているため、containerQuery.cssでメディアクエリーを使ってこれらのコンポーネントのレスポンシブ性を制御することにしました。
@media (min-width: 50em) { main { grid-template-columns: 1fr 12rem; } } /*その他のCSS*/ @media (min-width: 650px) { .call-to-action { grid-template-columns: 1fr min(10rem, 1fr); } .call-to-action > :not(.button) { grid-column: 1 / 2; } .call-to-action > .button { grid-row: 1 / 3; grid-column: 2 / 3; align-self: center; justify-self: end; } }
これらのメディアクエリーは、特定のビューポートでWebページのレイアウトと.call-to-actionセクションをスタイル付けし、ビューポートが変更されるとコンポーネントを積み重ねたり展開したりします。
この方法は、Webページコンポーネントに対して統一した動作を望む場合には上手く機能します。
しかし、大きな問題が1つ発生します。ページの表示が大きい場合、サイドバーコンポーネントのボタンがテキストの横に表示されるのは最適なデザインではありません。
下の画像では、「READ MORE」というボタンがWebページからはみ出し、デザインを崩しています。
この問題は、
この状況では、特定のレスポンシブな動作を実現するためにモディファイアクラスを作成する必要があり、CSSコードが複雑化する可能性があります。CSSが複雑になりすぎるとメンテナンスが大変になることはよく知られています。
しかし、コンテナクエリーを使えば、わずかな変更で簡単にサイドバーのボタンをテキストの下に積み重ねることができます。
containerQuery.cssで以下の変更を加えます。
まず、main要素の直接の子要素をコンテナにします。これで子コンポーネントを個別に制御できます。
main > * { container-type: inline-size; }
次に、コメントアウトされたメディアクエリーをコンテナクエリーに置き換えます。
/* @media (min-width: 650px){ */ @container (min-width: 650px) { .call-to-action { grid-template-columns: 1fr min(10rem, 1fr); } .call-to-action > :not(.button) { grid-column: 1 / 2; } .call-to-action > .button { grid-row: 1 / 3; grid-column: 2 / 3; align-self: center; justify-self: end; } }
これでサイドバーのテキストコンポーネントとボタンコンポーネントが上下に積み重なるようになりました。これは、従来このような効果を出すのが難しかった状況からすると大きな改善です。
サイドバーの狭い範囲に.call-to-actionボタンが配置されるため、コンテナクエリーはこのコンポーネントを個別のユニットとして扱う必要があると検出します。そのため、コンポーネントのコンテキストに合わせて、より小さなコンパクトなボタンのスタイルが適用されます。これにより、ボタンはレイアウトからはみ出すことなく、常にコンテキストに適した見栄えになります。
下の画像のように、デザインの問題が解決され、「READ MORE」ボタンがWebページからはみ出なくなったのがわかります。
コンテナクエリーは、Webサイトのデザインを様々な状況に適応させるスマートなアシスタントのようなものです。Webサイトの様々な場面に合わせたテーラーメイドの服を用意しているようなものです。
DevToolsでのコンテナクエリーのデバッグ
コンテナクエリーのデバッグ準備として、Chrome Developer Toolsを使ってデモページを検査する方法を確認しましょう。
Chrome Developer Toolsを開きます。コンテインメント設定のある要素を右クリックし「Inspect」を選択するか、macOSではOption + ⌘ + J、Windows/Linuxではshift + CTRL + Jを押して開くこともできます。以下の画像のように、オプションをクリックすることもできます。
次に、コンテインメントが適用されている親要素に移動し、コンテナバッジをクリックします。このアイコンはグリッドやフレックスボックスのバッジに似ていて、Webページ上のコンテナ要素を素早く特定できます。以下の画像では、コンテナ要素のコンテナバッジをクリックしています。
コンテナバッジをクリックすると、親コンテナとそのクエリする子孫要素全体にオーバーレイが表示されます。このオーバーレイは、コンテナとその子孫がどのように配置されているかを視覚的に表しています。以下の画像は、コンテナ要素とその子孫のオーバーレイを示しています。
単に表示するだけでなく、DevToolsでコンテナクエリーを編集することもできます。要素をDynamic編集すると、コンテナの変更をリアルタイムで確認できます。たとえば、デモのaside要素を検査している際に、チェックボックスをクリックしてボタンの表示を変更すると、UIがすぐに変更されます。以下の画像では、「READ MORE」ボタンが変更されています。これは、デザインがリアルタイムで変更される様子を確認するのに便利です。
ブラウザサポート
この記事を執筆している時点での主要ブラウザのコンテナクエリーサポート状況は以下の通りです。
完全サポート
インラインサイズに基づくクエリとコンテナクエリーユニット値の機能は、最新のすべてのモダンブラウザエンジンで安定してサポートされています。
Chrome: 105
Edge: 105
Firefox: 110
Opera: 91
Safari: 16
部分サポート
しかし、コンテインメントされた要素のカスタムプロパティ値をスタイリングし、これらのCSSの値をクエリの子孫に継承させる機能はまだすべてのブラウザでサポートされていません。
Chrome: 111
Edge: 111
Firefox: N/A
Opera: 97
Safari: N/A
終わりに
コンテナクエリーについて確実な理解ができたと思います。レスポンシブWebデザインにおけるコンテナクエリーの力を活用する方法がわかったはずです。
メディアクエリーとコンテナクエリーの違い、コンテナクエリーの仕組み、実践的なデモを通した創造的な可能性、さらにはデバッグの手順まで、幅広く解説しました。
この知識があれば、様々な画面サイズやデバイスに対して、シームレスに適応するWebレイアウトを作成できるはずです。コンテナクエリーはデザインの機会の世界を開き、視覚的にも魅力的な優れたユーザーエクスペリエンスを提供するための力を与えてくれます。
Webデベロップメントの旅に出る際は、好奇心を持ち続け、コンテナクエリーを存分に活用してください。ハッピーコーディング!