本セッションの登壇者
セッション動画
鹿野 壮(Twitter: @tonkotsuboy_com)です。マネーフォワードでフロントエンドエンジニアをしていて、JavaScriptコードレシピ集という書籍や日経ソフトウェアの記事を執筆しています。
Media queriesによるレスポンシブ対応の課題
今回は**Container queries(コンテナクエリ)**について紹介しますが、その前に従来のMedia queries(@media
)によるレスポンシブ対応の課題についてお話します。

たとえば赤枠のようなSNSページのリアクションバー(「返信する」「いいね」などのリアクションとその数を表示するコンポーネント)を開発するとしましょう。

そして親要素の領域によって2種類のサイズ(「いいね」などのテキストあり/なしのもの)を用意するとします。

このように領域にあわせてサイズを変えるとき、@media
を使ってレスポンシブ対応することが考えられますが、@media
の基準はウィンドウの幅ですので、親要素の幅にあわせるには画面幅ごとに細かく条件分岐する必要があります。

画面幅1480px以上のとき … すべて「いいね」などのテキストあり
画面幅1080px以上1480px未満のとき … 上部エリアではテキストあり、 下部エリアは2カラム表示なのでテキストなし
画面幅859px以上1080px未満のとき … 上部ではテキストあり、下部エリアも1カラム表示に切り替わるのでテキストあり
画面幅825px以上859px未満のとき … 上部ではテキストあり、下部エリアの幅が狭くなるのでテキストなし
画面幅825px未満のとき … etc.
このように画面幅を基準に多くの条件分岐を考える必要があり、分岐を実現するには非常に多くの@media
を書く必要があります。

さらに注文が追加されることもあります。
- ナビゲーションバーの幅を変えたのでメインエリアの幅も変わった
- ナビゲーションは開閉機能をもたせたので、開いたときだけメインエリアの幅が変わる
- ○○pxのときだけレイアウトがおかしい

この例のように、@media
でウィンドウサイズだけを基準として複雑なレスポンシブ対応を行うには限界があります。

コンテナクエリの登場
そこで登場するのがコンテナクエリです。ウィンドウサイズを基準とするMedia queries(@media
)と異なり、コンテナクエリは任意の要素のサイズを基準にできます。

コンテナクエリの基準となる要素のことを「コンテナ」と呼び、さきほどの例の場合、リアクションバーが配置されている親要素を@container
のサイズの基準にできます。

大きなコンテナでは「いいね」などのテキストを表示して、小さなコンテナではテキストなし、アイコン/数値のみという条件分岐ができるようになります。

コンテナクエリを使う3ステップ
コンテナクエリを実際に使うには、3つのステップがあります。

基準となるコンテナの決定
今回の例では返信ボタンやいいねボタンを囲んでいる親要素である.reaction-bar
要素をコンテナとします。

ある要素をコンテナにしたい場合は、CSSでその要素にcontainer-type
かcontainer
プロパティを指定します。今回は.reaction-bar
要素に対してcontainer-type
を指定します。

方向の指定
今回はインライン方向(横幅)のサイズが大きいときにはテキストを表示、小さいときにはテキストを非表示にしたいので、インライン方向を基準にします。


どちらの方向を基準にスタイルを適用するかは、container-type
プロパティで指定できます。
container-type: inline-size;
…インライン(横)方向のみを監視container-type: size;
… インライン(横)/ブロック(縦)両方を監視



クエリを書く
@container
ルールを使ってコンテナクエリを記述します。下記のクエリを書くと、コンテナの幅が560px以下のときだけ内部のスタイルが適用されるようになります。

今回はコンテナの幅が560px以下のときだけ.text
要素を非表示にしたいので、@container (max-width: 560px) {}
で囲んだ中で.reason-bar .text
にdisplay: none;
を指定します。

デモをご覧いただければ、コンテナのサイズにあわせてテキストの表示・非表示が切り替わっていることが確認できます。

コンテナクエリとコンポーネント開発
コンテナクエリはReactなどのコンポーネント開発と非常に相性が良いです。

今回のリアクションバーのコンポーネント開発者は、コンポーネントがどこに配置されるかを考える必要はなく、コンポーネントが配置されるコンテナの広さだけを考えれば良くなります。

リアクションバーを作るコンポーネント開発者は、ウィンドウサイズを意識することなく、適切なコンテナサイズに対する適切な見た目だけを担保するよう、コンポーネントを開発します。全体を作る開発者にリアクションバーコンポーネントを渡して任意の場所に配置してもらえば、配置された場所のサイズにあわせて期待通りに表示されます。

コンテナに対する割合サイズ
さらに、コンテナの幅/高さに対する割合サイズ(cqw
/ cqh
)が使えるようになりました。
cqw
はコンテナの幅に対する割合サイズで、100cqw
がコンテナの幅の100%です。

cqh
はコンテナの高さに対する割合サイズで、100cqh
がコンテナの高さの100%です。

cqw
/ cqh
の活用例
次の例のように、コンテナ内に5文字(「古都めぐり」)のテキストをぴったり配置したい場合を考えます。

この場合、1文字分が100cqw
を5分割して20cqw
になるようにします。

そして左のようなHTML要素に対して、.text-container
にcontainer-type: inline-size;
を指定してコンテナとし、内部の.text-container .text
にfont-size: 19cqw;
を指定します。ちなみにfont-size: 20cqw;
とすると、コンテナサイズによってはFirefox / Safariできれいに5分割できない場合があるため、少し小さめの19cqw
にしておきます。

このようにcqw
を使ってフォントサイズを指定すると、親要素のサイズが変わってもかならず5文字がコンテナ内にぴったり均等に表示されます(デモ)。

対応ブラウザ
2023年2月14日にFirefox 110が@container
に対応したことにより、@container
、container-type
、cqw/cqh
はすべてのブラウザで利用できるようになりました。

まとめ
- コンテナクエリを使うと、ウィンドウではなく任意の要素のサイズを基準にレスポンシブ対応が可能
- コンテナのサイズさえ気にすればよいので、コンポーネント開発が便利になる
- コンテナクエリ関連のCSSは大きく3つ
container-type: inline-size;
… 基準となるコンテナ要素と方向の指定@container
… コンテナクエリcqw
/cqh
… コンテナに対する割合サイズ
詳細はZennでも解説していますので、ぜひご覧ください。

また、Twitter @tonkotsuboy_comでも最新フロントエンド技術について発信していますので、ぜひフォローしてください。

ご清聴ありがとうございました。