CSS コンテナクエリ(@container)の便利な使い方を解説
Post on:2023年5月16日
CSSのコンテナクエリとは、親コンテナに基づいてスタイルを定義できるCSSの新機能です。これによりコンポーネントを複数のコンテクストで使用でき、コンポーネント単位の実装も簡単になります。
2023年2月14日にFirefox 110にサポートされ、CSSのコンテナクエリはこれですべてのブラウザで利用できるようになりました。コンテナクエリの基礎知識と便利な使い方を紹介します。
Say Hello To CSS Container Queries
by Ahmad Shadeed
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
コンテナクエリの背景
私がフロントエンドのデベロッパーとして過ごした過去6年間で、今ほどCSSの機能に興奮したことはありません。コンテナクエリのプロトタイプが、Chrome Canaryのフラグで利用できるようになりました。Miriam Suzanneをはじめとする関係者皆さまの努力に感謝します。
CSSコンテナクエリのサポートについては、多くのジョークを目にした記憶がありますが、ついに実装されました。この記事では、なぜコンテナクエリが必要なのか、コンテナクエリを使用するとどのように作業が楽になるのか、そして何よりもよりパワフルなコンポーネントやレイアウトを実現できるのかを紹介したいと思います。
私と同じようにワクワクしているなら、さっそく始めましょう。
準備はいいですか?
メディアクエリの問題点
Webページはさまざまなセクションやコンポーネントで構成されていますが、わたし達はそれらをCSSメディアクエリを使用してレスポンシブ化しています。もちろんそれは悪いことではありませんが、メディアクエリには限界があります。
例えば、メディアクエリを使用して、コンポーネントの最小バージョンをスマホとデスクトップの両方に表示することができます。ほとんどの場合、レスポンシブWebデザインはビューポートやスクリーンサイズではありません。コンテナのサイズについてです。下記の例をご覧ください。
カードコンポーネントのレイアウト
カードコンポーネントを使用した典型的なレイアウトがあります。これには2つのバリエーションがあります。
- 垂直に積み重ねて配置(サイドバー)
- 水平に並べて配置(メイン)
このレイアウトをCSSで実装する方法はいくつかありますが、最も一般的なのは下記の通りです。ベースとなるコンポーネントを作成してから、そのバリエーションを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.c-article { /* デフォルトの積み重ねバージョン */ } .c-article > * + * { margin-top: 1rem; } /* 水平バージョン */ @media (min-width: 46rem) { .c-article--horizontal { display: flex; flex-wrap: wrap; } .c-article > * + * { margin-top: 0; } .c-article__thumb { margin-right: 1rem; } } |
水平バージョンを作成するために.c-article--horizontalを使用したことに注目してください。ビューポートの幅が46remより大きい場合、コンポーネントは水平方向のバリエーションに切り替える必要があります。
この実装は悪いことではありませんが、どういうわけか私は少し制限されているように感じます。コンポーネントがブラウザのビューポートやスクリーンのサイズではなく、親の幅に応じるようにしたいと考えます。
メインにデフォルトの.c-cardを使用したいと考えます。
どうなると思いますか?
カードは親の幅に応じて拡張されるため、大きすぎます。下記をご覧ください。
メインでデフォルトの.c-cardを使用した場合
この問題は、CSSコンテナクエリで解決できます。
コンテナクエリのおかげで望む結果を得ることができるようなりました。
親の幅が400pxを超える場合は、水平に切り替えられますか?
直接の親の幅が400pxより大きい場合は、水平に切り替える必要があることをコンポーネントに伝える必要があります。そのため、CSSは下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
<div class="o-grid"> <div class="o-grid__item"> <article class="c-article"> <!-- content --> </article> </div> <div class="o-grid__item"> <article class="c-article"> <!-- content --> </article> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.o-grid__item { container: layout inline-size; } .c-article { /* ここにデフォルトのスタイルを定義 */ } @container (min-width: 400px) { .c-article { /* 水平にするスタイル** ** カードスタイルの代わりに */ } } |
containerと@containerが、コンテナクエリです!
コンテナクエリはどのように役立つのか?
CSSコンテナクエリを使用すると、上記の問題を解決し、流動的なコンポーネントを作成できます。つまり、コンポーネントを狭い親に入れれば積み重ねバージョンになり、広い親に入れれば水平バージョンになります。繰り返しになりますが、これらはすべてビューポートの幅には依存しません。
イメージとしてはこんな感じです。
左: メディアクエリ、右: コンテナクエリ
パープルの輪郭は親の幅を表しています。親の幅が大きくなると、コンポーネントもそれに応じて変化することに注目してください。
すごいと思いませんか?
これがCSSコンテナクエリの性能です。
CSSのコンテナクエリは、2023年2月14日にFirefox 110でサポートされ、すべてのブラウザにサポートされました。
コンテナクエリの基礎知識
コンテナクエリの最初のステップは、containerプロパティを追加することです。コンポーネントは親の幅に応じて変化するので、ページ全体ではなく、影響を受けるエリアのみを再描画するようにブラウザに伝える必要があります。containerプロパティを使用して、そのことを事前にブラウザに知らせることができます。
inline-sizeという値は、親の幅の変更にのみに反応することを意味しています。block-sizeも使用してみましたが、まだ機能しません。私が間違っていたらご指摘ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<div class="o-grid"> <div class="o-grid__item"> <article class="c-article"> <!-- コンテンツ --> </article> </div> <div class="o-grid__item"> <article class="c-article"> <!-- コンテンツ --> </article> </div> <!-- 他の記事 --> </div> |
1 2 3 |
.o-grid__item { container: layout inline-size; } |
最初のステップは、これだけです。
.o-grid__item要素をその中の.c-articleの包括親として定義しました。
次のステップでは、コンテナクエリを機能させるスタイルを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 |
.o-grid__item { container: layout inline-size; } @container (min-width: 400px) { .c-article { display: flex; flex-wrap: wrap; } /* 他のスタイルを記述 */ } |
この@containerは.o-grid__item要素で、min-width: 400pxはその幅です。さらに、スタイルを追加することもできます。
これだけの実装でコンテナクエリがどのように機能するかをご覧ください。
コンテナクエリがどのように機能するか
このデモには以下のスタイルがあります。
- デフォルトは、サムネイルとテキストが積み重なったカード。
- サムネイルが小さい横長のカード。
- サムネイルが大きい横長のカード。
- 親のサイズがさらに大きい場合は、特集記事であることを示すためにヒーローのようなスタイルになります。
次に、CSSコンテナクエリの使用例を見ていきましょう。
コンテナクエリの使用例
それでは、CSSコンテナクエリの使用例を見てみましょう。
コンテナクエリとCSS Gridのauto-fit
CSS Gridでauto-fitを使用すると、予期しない結果になる場合があります。例えば、コンポーネントの幅が広くなりすぎて、コンテンツが読みにくくなるなどです。
少しコンテクストを与えるために、CSS Gridのauto-fitとauto-fillの違いをビジュアルで示します。
上: auto-fill、下: auto-fit
auto-fitの場合、アイテムが拡張されて使用可能なスペースがいっぱいになります。そしてauto-fillの場合はアイテムは拡張せず、代わりに空きスペース(右端の点線のアイテム)ができます。
これがCSSコンテナクエリと関係あるの?と思われるかもしれません。各グリッドアイテムはコンテナであり、それが拡張された時(つまりauto-fitを使用している時)、それに基づいてコンポーネントを変更する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="o-grid"> <div class="o-grid__item"> <article class="c-article"></article> </div> <div class="o-grid__item"> <article class="c-article"></article> </div> <div class="o-grid__item"> <article class="c-article"></article> </div> <div class="o-grid__item"> <article class="c-article"></article> </div> </div> |
1 2 3 4 5 |
.o-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 1rem; } |
4つのアイテムがあり、下記のように表示されます。
auto-fitを使用した場合
アイテムの数が少なくなると、下記のようにアイテムの幅が広くなります。その理由はauto-fitを使用しているからです。アイテムが3つだと見栄えはいいですが、2つ1つと少なくなると幅が広すぎるため、見栄えはよくありません。
アイテムの数が少なくなると、幅が広くなる
各アイテムが親の幅に応じて変化するとしたらどうなると思いますか? そうすればauto-fitのメリットを十分に得ることができます。必要なことは下記の通りです。
グリッドアイテムの幅が400pxより大きい場合、アイテムを水平スタイルに切り替える必要があります。これを実現するには、次のようにします。
1 2 3 4 5 6 7 8 9 10 |
.o-grid__item { container: layout inline-size; } @container (min-width: 400px) { .c-article { display: flex; flex-wrap: wrap; } } |
また、グリッド内のアイテムが唯一である場合には、アイテムをヒーローセクションで表示してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.o-grid__item { container: layout inline-size; } @container (min-width: 700px) { .c-article { display: flex; justify-content: center; align-items: center; min-height: 350px; } .card__thumb { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; } } |
親の幅に応じて各アイテムが変化
これで終わりです。
親の幅に反応するコンポーネントができ、どんな状況でも動作するようになりました。これはすごいことだと思いませんか?
See the Pen
Article - QC by Ahmad Shadeed (@shadeed)
on CodePen.
サイドバーとメイン
<aside>のような幅が狭いコンテナでも機能するように、コンポーネントを調整する必要がある場合がよくあります。
これにぴったりなのが、ニュースレターのセクションです。幅が狭い場合は積み重ね、幅が広い場合には水平方向に広げる必要があります。
ニュースレターのセクション
上記のモックアップには、2つの異なるコンテクストが存在します。
- asideのセクション
- mainのセクション
コンテナクエリがなければ、CSSでバリエーションクラス(例えば.newsletter--stackedなど)を用意しないと、これは実装できません。
Flexboxで十分なスペースがない場合にアイテムを強制的にラップさせることは承知していますが、それだけでは不十分です。実装にはさらに多くのコントロールが必要です。
- 特定の要素を非表示にする
- ボタンを全幅にする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
.newsletter-wrapper { container: layout inline-size; } /* デフォルトの積み重ねバージョン */ .newsletter { /* CSS styles */ } .newsletter__title { font-size: 1rem; } .newsletter__desc { display: none; } /* 水平バージョン */ @container (min-width: 600px) { .newsletter { display: flex; justify-content: space-between; align-items: center; } .newsletter__title { font-size: 1.5rem; } .newsletter__desc { display: block; } } |
これで下記のように実装されます。
ニュースレターのデモ
実際の動作は、下記でご覧ください。
See the Pen
Newsletter - QC by Ahmad Shadeed (@shadeed)
on CodePen.
ページネーション
ページネーションは、コンテナクエリを使用するのに適しているが分かりました。「前へ」と「次へ」のボタンを用意し、十分なスペースがある場合はそれらを非表示にして、完全なページネーションを実装することができます。
親の幅に応じて、どのように変化するかをご覧ください。
親の幅に応じて変化するページネーション
これを実装するには最初にデフォルトのスタイル(積み重ね)を定義してから、他の2つのスタイルを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
.wrapper { container: layout inline-size; } @container (min-width: 250px) { .pagination { display: flex; flex-wrap: wrap; gap: 0.5rem; } .pagination li:not(:last-child) { margin-bottom: 0; } } @container (min-width: 500px) { .pagination { justify-content: center; } .pagination__item:not(.btn) { display: block; } .pagination__item.btn { display: none; } } |
実際の動作は、下記でご覧ください。
See the Pen
Pagination - QC by Ahmad Shadeed (@shadeed)
on CodePen.
プロフィールのカード
プロフィールのカード
これも複数のコンテクストで使用するのに適した例です。カードが小さな状態では小さなビューポートやサイドバーのようなコンテクストで機能し、大きな状態では2カラムのグリッドに配置するなどより大きなコンテクストで機能します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.p-card-wrapper { container: layout inline-size; } .p-card { /* デフォルトのスタイルを記述 */ } @container (min-width: 450px) { .meta { display: flex; justify-content: center; gap: 2rem; border-top: 1px solid #e8e8e8; background-color: #f9f9f9; padding: 1.5rem 1rem; margin: 1rem -1rem -1rem; } /* 他のスタイルがあればここに記述 */ } |
これで、単一のメディアクエリを使用せずに、構成要素の言葉が異なるコンテクストでどのように表示されるかを確認できます。
カードはサイドバーとメインで表示が最適化される
実際の動作は、下記でご覧ください。
See the Pen
Profile Card - QC by Ahmad Shadeed (@shadeed)
on CodePen.
フォーム要素
フォームでの使用例についてはまだ深く考えていませんが、頭に浮かんだのは、ラベルと入力欄を水平に並べるのではなく、積み重ねに切り替えることです。
上: 親コンテナが大きい場合、下: 親コンテナが小さい場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.form-item { container: layout inline-size; } .input-group { @container (min-width: 350px) { display: flex; align-items: center; gap: 1.5rem; input { flex: 1; } } } |
実際の動作は、下記でご覧ください。
See the Pen
Form Input - CQ by Ahmad Shadeed (@shadeed)
on CodePen.
コンテナクエリのデバッグ方法
CSSコンテナクエリが役立つ使用例をいくつか紹介しましたが、コンポーネントをデバッグするにはどうすればよいでしょうか?
ありがたいことに、コンポーネントの親のresizeプロパティを使用して簡単にデバッグすることができます。
親要素にresizeプロパティを加える
1 2 3 4 5 |
.parent { container: layout inline-size; resize: horizontal; overflow: auto; } |
Bramus Van Dammeによる素晴らしい記事で、このテクニックを知りました。
デベロッパーツールでコンテナクエリをデバッグするのは簡単ですか?
答えは、ノーです。
@container (min-width: value)は表示されません。しかし、サポートされるのは、時間の問題だと思います。
フォールバックを提供することは可能ですか?
はい、特定の方法でフォールバックを提供することが可能です。これを行う方法を説明する2つの素晴らしい記事があります。
まとめ
私はCSSコンテナクエリについて学び、ブラウザで試してみて楽しかったです。まだ正式にはサポートされていませんが、ブラウザで試してみるには絶好の機会です。
フロントエンドのデベロッパーとしてわたし達の仕事の一部は、このような機能の実装に取り組む人々を支援することです。実際にテストしてみることで、その機能が主要なブラウザでサポートされるようになったときの問題は少なくなるでしょう。
コメントや提案があれば、@shadeed9までお願いします。
sponsors