CSSの進化が早い! スタイルクエリ(@container style())の基礎知識と便利な使い方を解説
Post on:2023年3月14日
コンテナクエリがすべてのモダンブラウザの安定版でサポートされ、これからはメディアクエリからコンテナクエリを使用する機会が増えてくると思います。
コンテナクエリは親コンテナに基づいてスタイルを定義できるものですが、親のサイズによるクエリだけではありません。親のスタイル値によるクエリ(スタイルクエリ)も可能です。スタイルクエリの基礎知識と便利な使い方を紹介します。
スタイルクエリを使用すると、複数のバリエーションを持つ再利用可能なコンポーネントが簡単に実装できます。
Getting Started with Style Queries
by Una Kravets
下記は各ポイントを意訳したものです。
※元サイト様のライセンスに基づいて翻訳しています
CSSのスタイルクエリとは
親コンテナに基づいてスタイルを定義できるコンテナクエリとコンテナクエリの各単位は、ついにすべてのモダンブラウザの安定版でサポートされました。
コンテナクエリについて詳しくは、下記をご覧ください。
しかし、CSS Containment Module Level 3は、サイズによるクエリだけではありません。親のスタイル値によるクエリも可能です。Chrome 111でサポートされたスタイルクエリは、プロパティ値を親要素にクエリできるようになります。
これは、CSSでスタイルをさらに論理的に制御できることを意味します。アプリケーションのロジックやデータ層とスタイルからより適切に分離できるようにします。
サイズとスタイルのクエリをカバーするCSS Containment Module Level 3の仕様では、font-weight: 800;
のようなプロパティと値を含むあらゆるスタイルを親からクエリできます。ただし、スタイルクエリは現在のところCSSのカスタムプロパティ値に対してのみ機能します。これはスタイルを組み合わせたり、データとデザインを分離したりするのに非常に便利な機能です。
それでは、カスタムプロパティでスタイルクエリを使用する方法を解説します。
スタイルクエリの使い方
下記のHTMLをご覧ください。
1 2 3 4 5 6 7 |
<ul class="card-list"> <li class="card-container"> <div class="card"> ... </div class="card"> </li> </ul> |
CSSのスタイルクエリを使用するには、最初にコンテナ要素を設定する必要があります。これは、直接または間接の親にクエリを実行するかどうかによって、異なるアプローチが必要になります。
直接の親に対するクエリ
直接の親に対するクエリ
スタイルクエリとは異なり、.card
がその直接の親のスタイルをクエリできるようにするために、.card-container
にcontainer-type
またはcontainer
プロパティを使用した包含する必要はありません。ただし、スタイル(この場合はカスタムプロパティ値)をコンテナ(この場合は.card-container
)またはDOM内でスタイルを設定する要素を含む任意の要素に適用する必要があります。クエリで指定したスタイルをそのクエリを使用してスタイルしている直接の要素に適用してしまうと、無限ループを引き起こす可能性があるためです。
親を直接クエリするには、下記のように記述します。
1 2 3 4 5 6 7 8 |
/* .card-containerの-themeの値に基づいて、.cardをスタイルする */ @container style(--theme: warm) { .card { background-color: wheat; border-color: brown; ... } } |
スタイルクエリでは、クエリをstyle()
で包んでいることに注目してください。これはサイズの値とスタイルを明確にするためです。
たとえば、コンテナの幅を指定するクエリは、@container (min-width: 200px) { … }
と記述します。これは親コンテナの幅が200px
以上であればスタイルを適用します。ただし、min-width
はプロパティにすることもできるので、スタイルクエリを使用してmin-width
のCSS値をクエリすることもできます。そのため違いを明確にするために、style()
のラッパーを使用して、@container style(min-width: 200px) { … }
と記述します。
間接の親に対するクエリ
間接の親、つまり直接の親ではない要素のスタイルをクエリする場合は、その要素にcontainer-name
を与える必要があります。
たとえば、.card-list
にcontainer-name
を与え、スタイルクエリでそれを参照することで、.card-list
のスタイルに基づいて.card
にスタイルを適用できます。
1 2 3 4 5 6 |
/* .card-listの--moreGlobalVarの値に基づいて、.cardをスタイルする */ @container cards style(--moreGlobalVar: value) { .card { ... } } |
通常、コンテナに名前を与えてクエリが何を参照するのかを明確にし、コンテナに簡単にアクセスできるようにすることがベストプラクティスです。これが役立つ例として、.card
内の要素に直接スタイルを設定したい場合があります。.card-container
に名前が付いたコンテナがないと、直接クエリを実行することができません。
しかし、これらすべては実際にはもっと理にかなっています。いくつか例を見てみましょう。
スタイルクエリで実装したUIコンポーネント 1
スタイルクエリで実装したUIコンポーネント
スタイルクエリは複数のバリエーションを持つ再利用可能なコンポーネントやすべてのスタイルを制御できなくても特定のケースで変更するコンポーネントで特に役立ちます。
この例では、同じカードコンポーネントを共有する一連のプロダクトのカードが表示されています。一部のカードには「New」「Low Stock」などが与えられていますが、これは--details
というカスタムプロパティによってトリガーされています。プロダクトが在庫不足の「Low Stock」である場合、カードのボーダーはレッドになります。このタイプの情報はサーバーでレンダリングされる可能性が高いですが、下記のようにインラインのスタイルでカードで適用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<div class="product-list"> <div class="product-card-container" style="--detail: new"> <div class="product-card"> <div class="media"> <img .../> <div class="comment-block"></div> </div> </div> <div class="meta"> ... </div> </div> <div class="product-card-container" style="--detail: low-stock"> ... </div> <div class="product-card-container"> ... </div> ... </div> |
この構造化されたデータがあれば、値を-detail
に渡すことができ、CSSのカスタムプロパティでスタイルを適用できます。
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 |
@container style(--detail: new) { .comment-block { display: block; } .comment-block::after { content: 'New'; border: 1px solid currentColor; background: white; ... } } @container style(--detail: low-stock) { .comment-block { display: block; } .comment-block::after { content: 'Low Stock'; border: 1px solid currentColor; background: white; ... } .media-img { border: 2px solid brickred; } } |
実際の動作は、下記のデモをご覧ください。
See the Pen
Style CQ Demo -- Commerce by Una Kravets (@una)
on CodePen.
このCSSで--detail: low-stock
と--detail: new
を適用できますが、コードブロックに冗長な部分があることに気がついたかもしれません。現在のところ、@container style(--detail)
で--detail
の有無を照会する方法はありません。これによりスタイルの共有が向上し、繰り返しを少なくすることができます。この機能は現在、ワーキンググループで議論されています。
スタイルクエリで実装したUIコンポーネント 2
上記の例では、スタイルを適用するために複数の可能な値を持つ1つのカスタムプロパティを使用しました。しかし、複数のカスタムプロパティを使用してクエリを実行することで、それを混ぜることもできます。
お天気カードの例を見てましょう。
スタイルクエリで実装したUIコンポーネント
お天気カードの背景のグラデーションやアイコンのスタイルを設定するには、「cloudy」「rainy」「sunny」などの天気情報を探します。
1 2 3 4 5 6 7 8 9 10 |
@container style(--sunny: true) { .weather-card { background: linear-gradient(-30deg, yellow, orange); } .weather-card:after { content: url(<data-uri-for-demo-brevity>); background: gold; } } |
こうすることで、天気情報に応じたスタイルを各カードに設定できます。しかし、メディアクエリのようにand
を使用することでカスタムプロパティの組み合わせに対してスタイルを設定することもできます。
たとえば、曇りと晴れの両方がある日には「cloudy」「sunny」をand
で記述します。
1 2 3 4 5 6 7 8 9 10 |
@container style(--sunny: true) and style(--cloudy: true) { .weather-card { background: linear-gradient(24deg, pink, violet); } .weather-card:after { content: url(<data-uri-for-demo-brevity>); background: violet; } } |
実際の動作は、下記のデモをご覧ください。
See the Pen
Style Queries Demo - Weather Cards by web.dev (@web-dot-dev)
on CodePen.
スタイルクエリでさらに便利なのは、範囲値をクエリする機能です。
たとえば、--chanceOfRain: 50%
というデータを送信し、@container style(30% <= --chanceOfRain < 60%)
のように範囲を指定してスタイルを適用できます。このようにデザインやそのさまざまな組み合わせに対してより詳細な設定をすることができます。これもコンテナクエリのワーキンググループで活発に議論されているもう1つの機能です。
スタイルクエリでデータとデザインを分離する
上記の2つのデモは、適用されるスタイルからデータレイヤー(ページにレンダリングされるDOM)を分離する構造上の利点があります。スタイルはコンポーネントのスタイル内に存在する可能性のあるバリアントとして記述され、エンドポイントはコンポーネントのスタイル設定に使用するデータを送信できます。
1つ目では--detail
値を更新する単一の値を使用し、2つ目では--rainy
, --cloudy
, --sunny
の複数の値を使用しました。さらに、値を組み合わせることも可能で、--cloudy
と--sunny
をand
で設定することで曇りと晴れの両方のスタイルを適用することもできます。
JavaScriptによるカスタムプロパティの値の更新は、DOMモデルの設定中 (つまりフレームワークでのコンポーネント構築) にも<parentElem>.style.setProperty('--myProperty’, <value>)
による任意のタイミングでの更新にもシームレスに対応することができます。
下記は数行のコードでボタンの--theme
を更新し、スタイルクエリとカスタムプロパティ(--theme
)を使用してスタイルを適用するデモです。
スタイルクエリで実装したUIコンポーネント
実際の動作は、下記のデモをご覧ください。
See the Pen
Dynamic Style Query by web.dev (@web-dot-dev)
on CodePen.
スタイルクエリを使用してカードのスタイルを設定しています。カスタムプロパティの値を更新するために使用するJavaScriptは下記のようになります。
1 2 3 4 5 6 |
const themePicker = document.querySelector('#theme-picker') const btnParent = document.querySelector('.btn-section'); themePicker.addEventListener('input', (e) => { btnParent.style.setProperty('--theme', e.target.value); }) |
この記事で解説した機能は、スタイルクエリのほんの始まりにすぎません。動的にレスポンシブなインターフェイスを構築するために、コンテナクエリにはさらに多くのことを期待できます。特にスタイルクエリに関しては、まだ未解決の問題がいくつかあります。1つはカスタムプロパティ以外のスタイルに対するスタイルクエリの実装です。これはすでに現在の仕様レベルで実装されていますが、まだどのブラウザにも実装されていません。また、ブーリアンコンテキスト評価については未解決の問題が解決されたときに現在の仕様レベルに追加される予定ですが、範囲クエリに関しては次の仕様レベルに予定されています。
sponsors