2010 年に公開された Ethan Marcotte 氏の記事1でレスポンシブウェブデザイン(RWD)の手法が提唱されて以来、レイアウト幅に応じてスタイルを変更する方法を実現するには、メディアクエリ(@media
)を使用するのが一般的でした。
そして 2023 年現在、コンテナクエリ(@container
)が主要なブラウザでサポートされ、RWD の考え方が大きく変化しようとしています。
すでに多くの記事で解説されていますが、コンテナクエリの仕様は複雑です。この記事では、コンテナクエリの例を挙げながら、実際にどのような使い方ができるのか、使用する際にどのような懸念点が考えられるかといった点について考えていきます。
前提
見出し「前提」コンテナクエリは主要なブラウザでサポートされていますが 2、W3C の仕様では草案(Working Draft)段階のため、今後、実装方法や機能に変更が発生する可能性がある点を留意しておく必要があります。
コンテナクエリは「CSS Containment Module Level 3」に含まれていますが、このモジュールには contain
や content-visiblity
といった、おもにレンダリングの最適化に関連するプロパティが属しています。
「Containment」は「封じ込め」ですので、周囲の要素に影響を及ぼさない独立した要素に対して、封じ込めたスタイルを適用するイメージで考えるとよいかもしれません。
なお、W3C のドキュメントではコンテナクエリの基準となる要素のことを「Query Container(クエリコンテナ)」と表記されていますが、コンテナクエリと似ていてまぎらわしいので、この記事では「基準コンテナ」と呼ぶことにします。
本記事では、親要素や祖先要素のサイズに応じて子孫要素のスタイルを切り替える「Size Queries」にフォーカスします。スタイルに応じて子孫要素のスタイルを切り替える「Style Queries」については詳しくは取り上げません。
コンテナクエリの例
見出し「コンテナクエリの例」それではまず、コンテナクエリの例を見てみましょう。
以下はサンプル用のオーディオプレーヤーです(音声は再生できません)。スライダーを動かすと、プレーヤーのコンテナの幅が変わります。
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lorem
スライダーを操作してプレーヤーの幅を変更すると、以下の 3 つの変化が起こります。
- プレーヤーのレイアウト
- ボタンのサイズ
- テキストのサイズ
1. プレーヤーのレイアウト
見出し「1. プレーヤーのレイアウト」まず、一番大きな変化ですが、スライダーを動かして幅を 460px
以上にすると、プレーヤーのレイアウトが縦長から横長に切り替わります。
これは、プレーヤーを囲う要素(.player-container
)を基準に、コンテナクエリを指定しているためです。以下にコードを抜粋します。
<!-- HTML -->
<div class="player-container">
<div class="player">
<figure class="cover">
<!-- 画像 -->
</figure>
<div class="ui-container">
<div class="ui">
<!-- UI -->
</div>
</div>
</div>
</div>
<!-- CSS -->
<style>
.player-container {
container: player / inline-size;
}
@container player (inline-size >= 460px) {
.player {
display: grid;
grid-template-columns: 200px auto;
}
}
</style>
まず、container: player / inline-size
でコンテナ名とコンテナタイプを指定しています。
そのあとに指定している、@container
で、基準コンテナが inline-size >= 460px
のときに、子要素 .player
のスタイルを変更しています。
CSS については、プロパティ以降でも取り上げます。
2. ボタンのサイズ
見出し「2. ボタンのサイズ」320px
を超えたあたりで再生ボタンとその左右のボタンのサイズが切り替わります。
しかし、460px
でプレーヤーが横長レイアウトになると、再びボタンのサイズが小さくなりますが、520px
を超えると再び大きくなります。
これは、基準コンテナをプレーヤーではなく UI 部分に設定しているためです。以下のように .ui-container
が inline-size >= 320px
のときに、ボタンのサイズを変更しています。
<!-- HTML -->
<div class="player-container">
<div class="player">
<figure class="cover">
<!-- 画像 -->
</figure>
<div class="ui-container">
<div class="ui">
<!-- UI -->
</div>
</div>
</div>
</div>
<!-- CSS -->
<style>
.ui-container {
container: ui / inline-size;
}
@container ui (inline-size >= 320px) {
.play {
width: 60px;
}
:where(.prev, .next) {
width: 20px;
}
}
</style>
このように、ひとつのコンポーネントであっても、複数のコンテナクエリを設定することができるので、文脈(そのときの基準コンテナのサイズ)に応じてスタイルを切り替えることができます。
なお、container-name
自体は任意で、指定しない場合には祖先要素のなかの一番近い基準コンテナが対象となります。
ただ、この例のように複数の基準コンテナが存在する場合には、container-name
を明示したほうがコードの可読性や予測可能性は高まります。
3. テキストのサイズ
見出し「3. テキストのサイズ」テキストのサイズも、この .ui-container
を基準コンテナとしていますが、ボタンと比較するとなめらかにサイズが変化します。
ここで、さきほどのサンプルから画像要素を取り除き UI のみにします。この状態でスライダーを動かすと、その変化がわかりやすいと思います。
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Lorem
このテキストサイズのなめらかな変化は「Fluid Typography」と呼ばれる手法を用いています。従来はビューポート幅に対して相対的にフォントサイズを変更することがありましたが、この例では基準コンテナの幅を起点としています。
対象となる部分の CSS を抜き出します。
詳細は割愛しますが、clamp()
関数を使用して、最小値・推奨値・最大値を指定しており、推奨値に cqi
を使用しているところがポイントです。
cqi
は、基準コンテナの inline-size
(書字方向が横書きの場合の横軸のサイズ)で、この値はパーセントです。4cqi
であれば、コンテナの幅 4% 分のサイズということになります。
なお、この実装方法は以下の記事を参考にしています。
このように、コンテナクエリでは、親要素や祖先要素のサイズに応じて子孫要素のスタイルを指定できるため、従来のビューポート幅を起点とした切り替えでは不可能だった、文脈に応じたスタイルの切り替えが実現できます。
プロパティ
見出し「プロパティ」基準コンテナに使用する CSS プロパティは以下のとおりです。
CSS プロパティ | 内容 |
---|---|
container-type | Containment(封じ込め)の対象軸 |
container-name | 基準コンテナの名前 |
container | container-type と container-name のショートハンド |
container-type
この見出しのリンクcontainer-type
では、基準コンテナの対象軸を指定しますが、このプロパティに size
もしくは inline-size
の値を指定した要素が基準コンテナになります。
container-type: size
では、横軸・縦軸ともに対象となるcontainer-type: inline-size
では、横軸が対象となる(書字方向が横書きの場合)container-type: normal
は、デフォルト値でサイズ指定の基準コンテナの対象外
ここで container-type: size
を指定すると、横軸・縦軸ともに封じ込めの対象となり、高さを明示しないと領域が確保されなくなります。そのため、container-type: inline-size
を指定するケースがほとんどでしょう。
なお、container-type: inline-size
であっても、幅を明示しなければならないケースもありますが、こちらは後述します。
container-name
この見出しのリンクcontainer-name
は任意ですが、名前を付けることで、@container
を使用するときに明示的に紐づけることができます。@container
で名前を指定しない場合には、もっとも近い container-type
を指定した祖先要素が対象になります。
container
この見出しのリンクcontainer
はこれらのショートハンドプロパティです。スラッシュ(/
)で区切って、container-type
と container-name
をまとめて指定できます。
コンテナクエリの注意点
見出し「コンテナクエリの注意点」コンテナクエリを使用する際に注意する点としては、基準コンテナに指定した要素自身にはコンテナクエリを指定できないという点です。
ただし、基準コンテナの擬似要素(::before
や ::after
など)には、コンテナクエリを指定できます。
@container
この見出しのリンクコンテナクエリ(@container
)で指定できるサイズ特性は以下のとおりです。
サイズ特性 | 内容 |
---|---|
width | 横幅 |
height | 高さ |
inline-size | インラインサイズ(書字方向が横書きの場合、横幅) |
block-size | ブロックサイズ(書字方向が横書きの場合、縦幅) |
aspect-ratio | アスペクト比 |
orientation | 縦長(portrait ) / 横長(landscape ) |
なお、高さやブロック軸に関連するサイズ特性は、基準コンテナに container-type: size
を指定しないと有効にならないので注意が必要です。この条件には、aspect-ratio
と orientation
も含まれます。
単位
見出し「単位」コンテナクエリのなかでは、以下の特別な単位が使用できます。
単位 | 内容 |
---|---|
cqw | 基準コンテナの幅の 1% |
cqh | 基準コンテナの高さの 1% |
cqi | 基準コンテナのインラインサイズの 1% |
cqb | 基準コンテナのブロックサイズの 1% |
cqmin | cqi か cqb の小さい方の値 |
cqmax | cqi か cqb の大きい方の値 |
前述の Fluid Typography の例で cqi
を使用しましたが、ビューポート幅 vw
(もしくは vi
)と同じように、コンテナの幅に応じたサイズ指定が可能になります。
コンテナクエリの特長
見出し「コンテナクエリの特長」以下の 3 つの観点から、コンテナクエリの特長を考えてみます。
- 関心の分離
- 再利用性
- 柔軟性
関心の分離
見出し「関心の分離」メディアクエリでは、ブレイクポイント単位でスタイルを指定・追加しています。これは、スタイルを切り替える起点がビューポート幅になり、トップダウンのアプローチといえます。
対して、コンテナクエリではコンテナの幅を起点にスタイルを切り替えることになるので、コンポーネント単位でスタイルが完結します。メディアクエリと比較すると、まさにコンテナを積み上げていくようなボトムアップのアプローチといえます。
レスポンシブのスタイルをもコンポーネントに封じ込めることができるので、独立性が高まっていると考えられるでしょう(関心の分離)。このことによりコンポーネントの保守性が高まります。
以上より、コンテナクエリはコンポーネント指向で設計するプロジェクトに最適です。一方で、スタイルがコンポーネント単位で完結しないような要素に指定すると、かえって複雑性が増してしまうかもしれません。
再利用性
見出し「再利用性」メディアクエリでは、同じようなスタイルのコンポーネントでも、使用する領域のサイズに応じてスタイルを調整するケースがありました。
例えば、あるコンポーネントで広い領域に配置するときには、.--large
のようなモディファイアを付与して対応することがあります。
コンテナクエリでは、そのときのコンポーネントのサイズによってスタイルが決まります。そのため、モディファイアのような個別の調整を必要としないため、再利用性が高まります。
この強みは、特に CSS グリッドを使用したレイアウトで効力を発揮するでしょう。
柔軟性
見出し「柔軟性」メディアクエリでは、ブレイクポイントごとに用意されたデザインカンプに基づいてスタイルを指定することが一般的ですが、その中間サイズで補完しきれないとバランスの悪い見た目になります。
コンテナクエリでは、ビューポート幅ではなくコンテナの幅を起点にスタイルが切り替わるため、より柔軟でスムーズなスタイルの変化を付けることが可能になります。
コンテナクエリの課題
見出し「コンテナクエリの課題」コンテナクエリはこれまでの RWD の考え方とは異なる手法のため、現時点ではいくつかの課題や懸念点が挙げられます。
デザイン
見出し「デザイン」RWD を前提としたデザインでは、これまではブレイクポイントごとにデザインカンプを用意することが一般的でしたが、コンテナクエリを導入する場合には、以下の点を考える必要があります。
- どの要素を基準コンテナにするか
- どのサイズ(横幅)で切り替えるか
- ビューポート幅で切り替えるパターンも含まれるか
Figma であればコンポーネントプロパティを使用することになると思いますが、前述したオーディオプレーヤーの例のように、プレーヤー全体と UI 部分といったように、複数の基準コンテナを持つ場合には構造が複雑になるため、デザイナーの負担が大きくなります。
コンテナクエリの使い方にもよりますが、デザインとコーディングの担当者が異なる場合、何かしらのガイドラインを用意するか、頻繁にコミュニケーションが取れる環境が求められそうです。
検証
見出し「検証」従来のメディアクエリによるスタイルの切り替えでは、コーディングがひととおり完了した時点で、ブレイクポイントのパターンごとに検証する方法が一般的でした。
しかし、コンテナクエリの場合には、基準コンテナごとにスタイルの切り替えが可能になるので、その検証をどのように実施すれば品質を確保できるのかという課題があります。
UI コンポーネントを管理するツールを導入しているのであれば、コンポーネントごとに検証することが可能ですが、その場合でも、周囲の要素との組み合わせや使用するレイアウトによって、バランスの調整が必要になるかもしれません。
2023/07/12 追記 検証用の Bookmarklet を作成しました。
まぜるな危険
見出し「まぜるな危険」同じ要素にコンテナクエリとメディアクエリの両方を指定することができますが、責任の所在がわからなくなるので、極力避けたほうがよいでしょう。
/* 🙅♂️ コンテナサイズとビューポートサイズを混在させない */
@container (inline-size >= 600px) {
.elem {
padding: 24px;
}
}
@media (width >= 1440px) {
.elem {
padding: 40px;
}
}
同じ要素に対する指定でなければ、スタイルシート内でコンテナサイズとビューポートサイズの指定が同居するケースも出てくると思いますが、何かしらの指針がなければ、デザインの一貫性やコードの可読性が損なわれる可能性があります。
なお、ビューポートサイズ以外のメディアクエリであれば、混在しても問題ありません。
@container (inline-size >= 600px) {
.elem {
padding: 24px;
}
}
/* 🙆♂️ ビューポートサイズ以外のメディアクエリであれば問題なし */
@media (any-hover: hover) {
.elem:hover {
background-color: var(--hover-color);
}
}
Containment(サイズの封じ込め)
見出し「Containment(サイズの封じ込め)」前述したように、container-type
を指定すると、インライン軸またはブロック軸のサイズが封じ込めの対象となります。
container-type: size
では、横軸・縦軸ともに対象となるcontainer-type: inline-size
では、横軸が対象となる(書字方向が横書きの場合)
周囲の要素とのレイアウトから独立することになるため、組み合わせによってはレイアウト崩れが発生します。
以下の例では、画像要素を子孫に持つ基準コンテナ.cover-container
を、親要素の display: flex
の指定で他の要素と横並びにします。
このとき、基準コンテナに明示的に横幅を指定していないと、隣り合う要素と重なってしまいます。これは container: inline-size
で、インライン軸のサイズが無視されてしまうためです。
LOREM
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
上記の例で「コンテナにサイズを指定」のチェックボックスをオンにすると、以下のスタイルが適用されるため、コンテナの領域が確保されます。
.cover-container {
container: cover / inline-size;
min-inline-size: 100px;
}
なお、具体的なサイズが決まっていないときには、inline-size: 100%
や width: 100%
を指定すれば、このような重なりを回避するこができますが、もちろん銀の弾丸ではありません。
DOM 要素
見出し「DOM 要素」コンテナクエリのためだけに基準コンテナの要素(<div>
など)を追加する場合には、DOM の構造が深くなります。これは、パフォーマンス面やコードの可読性に影響を及ぼします。
window.matchMedia
に相当するメソッドがない
この見出しのリンク2023/06/30 追記
JS でメディアクエリを参照するには、window.matchMedia
を使用しますが、現状、コンテナクエリにはこれに相当するメソッドが存在しません。
@container
において、px
のような JS で検知できる単位で指定されていれば、ResizeObserver
などを使用して対応できることもありますが、em
や ch
といった相対的な長さの単位で指定しているケースでは対応方法が見当たりません。
この問題は W3C の GitHub Issue でも議論されています。
レスポンシブイメージに対応していない
見出し「レスポンシブイメージに対応していない」2023/10/14 追記
<img>
要素の sizes
属性や、<source>
要素の media
属性によって、メディアクエリによる画像ファイルの出し分けができますが、コンテナクエリには対応していません。
もし、実現できたとしても、基準となるコンテナ要素(親要素)とコンテナクエリを指定している子要素の両方に sizes
属性を指定する必要があり、非常に複雑でメンテナンス性が悪くなります。
こちらも W3C の GitHub Issue でも議論されていますが、簡単に答えは出ないようです。
スタッキングコンテキストが形成される
見出し「スタッキングコンテキストが形成される」2024/06/22 追記
ほとんどの場合は悪影響を及ぼすことはありませんが、container-type
を指定するとスタッキングコンテキストが形成される点にも注意が必要です。
スタッキングコンテキストは要素の重なり順(Z軸)の基準となる概念です。position: relative
もしくは position: absolute
と z-index
の組み合わせが代表的な例ですが、そのほかにもさまざまなスタイル指定によって形成されます。
背景要素に z-index: 1
、重なっている正方形の要素に z-index: 10
を指定する例で考えてみます。HTML の構造と重なり順に関する CSS は以下のとおりです。
<div class="box">
<!-- 背景要素 -->
<div class="box-bg">z-index: 1</div>
<!-- 正方形の親要素 -->
<div class="square-parent">
<!-- 正方形 -->
<div class="square">z-index: 10</div>
</div>
</div>
<style>
.box {
position: relative;
}
.box-bg {
position: absolute;
z-index: 1;
}
.square {
position: relative;
z-index: 10;
}
</style>
以下はライブデモです。
container-type: inline-size
のチェックボックスを有効にすると、正方形を囲っている要素(.parent
)にその指定が適用され、見た目上の重なり順が変わります。
z-index: 1
z-index: 10
スタッキングコンテキストの詳細については、以下の記事をご参照ください。
これらに加えて、前提で述べたように、現時点では草案(Working Draft)段階のため、今後、実装方法や機能が変更される可能性がある点も留意しておいたほうがよいかもしれません。
このように、コンテナクエリには明確な特長がある反面、課題のほうが目立つ状態であるので、実際の案件で使用するには、まだまだリスクが大きいように感じます。
とはいえ、それで一蹴するのではなく、影響の少ないところから少しずつ取り入れて、使い方を探っていきたいと考えています。
関連記事
見出し「関連記事」コンテナクエリについては、以下の記事でも取り上げています。
参考文献
見出し「参考文献」本記事の作成にあたり、以下のウェブページを参考にしました。
- CSS Containment Module Level 3 | W3C(外部リンクを開く)
- A Primer On CSS Container Queries | Smashing Magazine(外部リンクを開く)
- Container Query Units and Fluid Typography | Modern CSS Solutions(外部リンクを開く)
- Say Hello To CSS Container Queries | Ahmad Shadeed(外部リンクを開く)