こんにちは!
今回のテーマはContainer Queriesです。
Container Queriesは、祖先要素として存在するコンテナのスタイルに応じてCSSを適用するための機能です。利用時は@container
で宣言します。
従来でもメディアクエリでブラウザのビューポート幅などに応じたCSS適用は可能でしたが、あくまでもブラウザやウィンドウ全体のスタイルに依存するものでした。Container Queriesでは画面の特定の範囲を
Container Queriesの利用例/表示箇所で見た目が変化するカードコンポーネント
カード形式で表示するコンポーネントの実装を例にContainer Queriesの利用例を見てみましょう。
次のようなスタイリングを想定します。
- カード形式のコンポーネントを表示し、スタイルを適用したい
- コンポーネントは、表示する箇所の幅が400px以下であれば見た目を変える
- 400pxを超える場合は画像と内容を横並びで表示する
- 400px以下の場合は画像と内容を縦並びで表示する
カードのHTMLは次のような内容です。
<div class="card">
<div>
<img class="avatar" src="./avatar.png" />
</div>
<div class="content">
<div class="name">mugi_uno</div>
<div class="description">
Cybozu, Inc. <br />
Frontend Expert Team <br />
@mugi_uno <br />
</div>
</div>
</div>
コンポーネントを配置する画面全体は次のような仕様とします。
- サイドバーとメインコンテンツの2カラムで構成されている
- サイドバーは160pxで固定
- メインコンテンツはウィンドウのサイズに応じて可変
画面全体のHTMLとCSSの一部を抜粋すると、次のような内容です。
<body>
<nav>
<h4>Side Bar</h4>
<div class="card tiny">
<!-- (省略)cardのコンテンツ -->
</div>
</nav>
<main>
<h4>Main Contents</h4>
<div class="card">
<!-- (省略)cardのコンテンツ -->
</div>
</main>
</body>
body {
display: grid;
grid-template-columns: 160px 1fr;
}
カードコンポーネントをサイドバーとメインコンテンツの両方に配置すると、ウィンドウ幅が広い場合は次のような表示を想定します。
一方で、ウィンドウサイズを狭めてメインコンテンツの幅が400px以下となった場合は、サイドバーと同様の見た目となります。次のようなイメージです。
従来の方法/メディアクエリを用いた場合
まず、Container Queriesを用いずに、従来はどう実装していたか確認してみましょう。
メディアクエリを用いると、ビューポート幅に応じてCSSを適用できます。しかしサイドバーの幅は常に固定幅ですので、ビューポート幅にかかわらずスタイルを適用する必要があります。こういった場合、class属性などを用いてスタイルを切り替える方法がよく用いられます。
次の例ではtiny
クラスを付与してスタイルを切り替えています。
<body>
<nav>
<h4>Side Bar</h4>
<div class="card tiny">
<!-- (省略)cardのコンテンツ -->
</div>
</nav>
<main>
<h4>Main Contents</h4>
<div class="card">
<!-- (省略)cardのコンテンツ -->
</div>
</main>
</body>
/* デフォルトのスタイル */
.card {
/* カード内容を横に並べて表示 */
display: grid;
grid-template-rows: 240px;
grid-template-columns: 240px 1fr;
}
.card.tiny {
/* カード内容を縦に並べて表示 */
grid-template-rows: 160px 1fr;
grid-template-columns: 1fr;
}
一方、メインコンテンツ側では、ビューポートに応じてスタイルを切り替える必要があります。しかし、サイドバーが常に一定幅を確保するため、それを考慮してメディアクエリを定義する必要があります。サイドバーの幅が160pxのため、次のような定義となります。
/* 切り替え基準の400px+サイドバーの幅160px */
@media screen and (width < calc(400px + 160px)) {
.card {
/* カード内容を縦に並べて表示 */
grid-template-rows: 160px 1fr;
grid-template-columns: 1fr;
}
}
※余談ですが、メディアクエリに利用しているwidth < calc(400px + 160px)
はRange Syntaxと呼ばれるメディアクエリのモダンな記法です。
メディアクエリを用いた実装の課題
メディアクエリを用いて、ひとまず期待どおりの見た目を実現できました。しかし同時に次のような課題も抱えています。
- メインコンテンツ側のCSSで、常にサイドバーの幅を意識する必要がある
tiny
クラスの付与に誤りがあると意図しない見た目となるtiny
時のCSSとメディアクエリ指定のCSSで内容が重複する
特に、現状の実装ではサイドバーの幅がメインコンテンツのメディアクエリ指定に影響を及ぼします。意図しない表示崩れを引き起こす要因となるため、スタイルの追加や変更時には注意が必要となります。
Container Queriesを用いた場合
では、Container Queriesではどうなるか見てみましょう。まず、サイドバーとメインコンテンツのレイアウトと幅を指定したうえで、コンテナとして扱われるようcontainer-type
を指定します。
body {
/* サイドバーとメインコンテンツを横並びにして幅を指定 */
display: grid;
grid-template-columns: 160px 1fr;
}
/* サイドバー */
nav {
container-type: inline-size;
}
/* メインコンテンツ */
main {
container-type: inline-size;
}
container-type
の値には、Container Queriesが何を基準に動作するかを指定します。inline-size
の場合、コンテナのインラインサイズに応じて計算されます。
続いて、カードコンポーネントのCSSでは、デフォルトのスタイルに加えて、@container
でコンテナに応じたスタイルを記述します。
/* デフォルトのスタイル */
.card {
/* カード内容を横に並べて表示 */
display: grid;
grid-template-rows: 240px;
grid-template-columns: 240px 1fr;
}
/* コンテナに応じたスタイル */
@container (width < 400px) {
.card {
/* カード内容を縦に並べて表示 */
grid-template-rows: 160px 1fr;
grid-template-columns: 1fr;
}
}
@container (width < 400px)
は
実装はこれだけで完了です。
なお、Container Queriesと組み合わせて使う機能として、コンテナのサイズに応じた数値単位であるcqw
やcqh
といったものも存在します。たとえば10cqw
で表現でき、よりコンテナに応じて柔軟にスタイルを適用できます。
container-nameによるコンテナ名の指定
Container Queriesで注意すべき点として、コンテナのネストが挙げられます。
コンテナ要素がネストした場合、祖先をたどり最も近いコンテナが利用されます。しかし、状況によってはcontainer-name
プロパティを使うことで、参照するコンテナと@container
で宣言されたCSSルールを名前で明示的に紐づけることができます。
さきほどの例では、次のように修正することでコンテナ名を指定できます。
nav {
container-type: inline-size;
/* コンテナ名を指定 */
container-name: layout;
}
main {
container-type: inline-size;
/* コンテナ名を指定 */
container-name: layout;
}
/* layout コンテナでのみ適用 */
@container layout (width < 400px) {
/* 省略 */
}
カードコンポーネントはレイアウトに関係する要素のサイズに応じてスタイルを切り替えたいので、"layout"というコンテナ名を定義しています。これにより、container-name
で"layout"を明示的に指定しない限り、カードコンポーネントのコンテナとしては利用されません。
たとえば次のようにmain
要素の中に.sub-container
クラスを持つ要素をネストし、幅は50%のコンテナとして配置したとします。しかし、.sub-container
を対象としたCSS定義にはcontainer-name
が含まれないため、カードコンポーネントのコンテナとしては機能しません。結果として、main
要素の幅だけを参照してContainer Queriesが適用されます。
.sub-container {
width: 50%;
container-type: inline-size;
}
<main>
<h4>Main Contents</h4>
<!-- カードコンポーネントのコンテナとしては機能しない -->
<div class="sub-container">
<div class="card">
<!-- (省略)cardのコンテンツ -->
</div>
</div>
</main>
もし@container
でのContainer Queries宣言時にcontainer-name
を利用しなかった場合、.sub-container
クラスを持つdiv 要素もカードコンポーネントのコンテナとして機能します。結果として、50%の幅も加味してContainer Queriesが適用されるため、よりウィンドウ幅が広い状態でも見た目が切り替わるような挙動となります。
Container Queriesを多くのシチュエーションで利用する際には、コンテナの衝突を回避するために極力container-name
を付与しておくほうが安全でしょう。
Container Style Queries
Container Queriesには実は複数の機能が存在します。ここまでに紹介した機能は、実は厳密にはContainer Queriesの中でもContainer Size Queriesと呼ばれ、コンテナのサイズに応じてスタイルを適用するものです。
一方で、Container Style Queriesと呼ばれる、コンテナが特定のスタイルを持つかを判定してスタイルを適用する機能が存在します。
Container Style Queriesの利用例/テーマの切り替え
Container Style Queriesの利用例のひとつとして、テーマの切り替えが挙げられます。実際の例を見ながらどういった機能か確認してみましょう。
次のようなスタイリングを想定します。
- コンテナでは2つのテーマに応じて背景色が黒か白で切り替わる
- コンテナの中にはヘッダが存在する
- コンテナの背景色が黒の場合:ヘッダの文字色を白にする
- コンテナの背景色が白の場合:ヘッダの文字色を黒にする
HTMLは次のような内容です。
<div class="dark-container">
<h2>Dark theme title</h2>
</div>
<div class="light-container">
<h2>Light theme title</h2>
</div>
このとき、Container Style Queriesを用いると次のようにスタイルを定義できます。
@container style(--bg-color: black) {
h2 {
color: white;
}
}
@container style(--bg-color: white) {
h2 {
color: black;
}
}
.dark-container {
--bg-color: black;
background-color: var(--bg-color);
}
.light-container {
--bg-color: white;
background-color: var(--bg-color);
}
実行結果は次の通りです。コンテナの背景色に応じてh2
要素の文字色が切り替わっていることが確認できます。
@container style(--bg-color: black)
のように、style()
にカスタムプロパティを指定することで、コンテナのカスタムプロパティの値を参照し、合致する場合にのみスタイルが適用されます。なお、Container Style Queriesにおけるcontainer-type
の指定は必要ありません。
上述の例ではコンテナの背景色に応じてスタイルを適用しましたが、カスタムプロパティに任意の文字列を指定して複数のテーマに対応させる、といったことも可能です。
例として、--theme
というカスタムプロパティを用いて、3種類のテーマを切り替えてみましょう。
CSSは次のような内容です。
@container style(--theme: dark) {
h2 {
color: white;
}
}
@container style(--theme: light) {
h2 {
color: black;
}
}
@container style(--theme: colorful) {
h2 {
color: orange;
}
}
.dark-container {
--theme: dark;
background-color: black;
}
.light-container {
--theme: light;
background-color: white;
}
.colorful-container {
--theme: colorful;
background-color: green;
}
HTMLにも全テーマに対応する表示を追加します。
<div class="dark-container">
<h2>Dark theme title</h2>
</div>
<div class="light-container">
<h2>Light theme title</h2>
</div>
<div class="colorful-container">
<h2>Colorful theme title</h2>
</div>
実行結果は次の通りです。3種類のテーマで切り替わっていることが確認できます。
上記では単純なテーマの切り替えを例として挙げていますが、Container Style Queriesではカスタムプロパティをどのような役割として定義するかによって、さまざまな目的に利用できます。たとえば、テーマ切り替え以外に次のような使い方も考えられるでしょう。
- gridの行列数に応じたグリッドレイアウトの調整
- PC・
モバイルに応じたスタイリング - 言語に応じたスタイルの切り替え
- A/
Bテストでの利用
ブラウザでのサポート状況
今回紹介した機能について、本記事掲載時点でのメジャーブラウザにおける利用可能バージョンは次の通りです。これ以降のバージョンであれば利用可能です。
機能 | Chrome | Edge | Safari | Firefox |
---|---|---|---|---|
Container Size Queries | 106 | 106 | 16 | 110 |
container-name |
105 | 105 | 16 | 110 |
Container Style Queries | 111 | 111 | 18 | (未対応) |
まとめ
今回はContainer Queriesを紹介しました。
昨今のWebフロントエンドでのUI開発ではコンポーネント指向が一般的となっています。それに伴いスタイリングもコンポーネントとして管理する需要が高まっていますが、Container Queriesを知っておくとJavaScriptに頼らずに多くのシチュエーションに対応可能な汎用性の高いコンポーネントも実現できるため、ぜひチャンスがあれば活用してみてください。