原文:https://ishadeed.com/article/say-hello-to-css-container-queries/
作者:Ahmad Shadeed
译者:Levix
私の過去 6 年間のフロントエンド開発のキャリアの中で、私はこれほどまでに CSS 機能に興奮したことはありません。コンテナクエリ機能は現在、Chrome Canary のフラグ(chrome://flags)で使用できるようになっています。これは Miriam Suzanne や他の友人たちの努力のおかげです。
私は CSS コンテナクエリに関する多くの冗談を見たことを覚えていますが、ついにそれが登場しました。この記事では、なぜ私たちがコンテナクエリを必要とするのか、どのようにそれがあなたの生活(CSS コーディング)を楽にし、最も重要なことは、より強力なコンポーネントやレイアウトを実現できるかを説明します。
もしあなたも私と同じように興奮しているなら、始めましょう。準備はいいですか?
CSS メディアクエリの問題#
ウェブページは異なるコンテンツやコンポーネントで構成されており、私たちは CSS メディアクエリを使用してそれをレスポンシブにすることができます。これは問題ないように見えますが、いくつかの制限があります。たとえば、メディアクエリを使用して、モバイルデバイスとデスクトップデバイスでコンポーネントの最小化バージョンを表示することができます。
通常、レスポンシブウェブデザインはビューポート(viewport)や画面サイズに基づくのではなく、コンテナサイズに基づいています。以下の例を参照してください:
非常にクラシックなカードコンポーネントのレイアウトがあり、2 つのレイアウト方式があります:
- スタック型(上図左側参照)
- 横型(上図中央領域参照)
CSS には上記のレイアウトを実現するためのさまざまな方法がありますが、最も一般的な方法は以下のコードのように、基本コンポーネントを作成し、それに基づいて適応させる必要があります。
.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
クラスを使用した場合、何が起こるでしょうか?カードはその親コンテナの幅に拡張されるため、全体のレイアウトは大きく見えます。以下の図を参照してください:
私たちは CSS コンテナクエリを使用してこの問題を解決できます(はい、これが究極の解決策です)。それらを深く掘り下げる前に、私たちが望む結果を理解しましょう。
私たちはこのコンポーネントに、直属の親コンテナの幅が 400px を超える場合、横スタイルに切り替える必要があることを伝える必要があります。以下は CSS コードの設定です:
<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>
.o-grid__item {
contain: layout inline-size;
}
.c-article {
/* デフォルトスタイル */
}
@container (min-width: 400px) {
.c-article {
/* 記事をカードスタイルではなく横スタイルにする */
}
}
CSS コンテナクエリはどのように私たちを助けるのか?#
警告:CSS コンテナクエリは現在、Chrome Canary ブラウザの実験的フラグでのみサポートされています。
CSS コンテナクエリを使用することで、上記の問題を解決し、スムーズなコンポーネントを作成できます。これは、コンポーネントを狭い親コンテナに置くと、それがスタックバージョンに変わり、広い親コンテナに置くと、それが横バージョンに適応することを意味します。そして、これらの適応変化はビューポートの幅とは独立しています。
以下は私のビジョンです。
紫の輪郭は親コンテナの幅を表しています。注意してください、幅が広くなると、このコンポーネントがどのように適応するか?これが CSS コンテナクエリの強力な点です。
コンテナクエリはどのように機能するのか#
私たちは現在、Chrome Canary でコンテナクエリを試しています。chrome://flags
にアクセスして「container queries」を検索し、それを有効にしてください(訳者注:Chrome 94 バージョン以降でも正常に使用できます)。
最初のステップは contain
属性を追加することです。コンポーネントはその親コンテナの幅に基づいて適応するため、ブラウザに影響を受ける領域だけを再描画するように指示する必要があります。contain
属性を使用することで、ブラウザに事前にこれを知らせることができます。
inline-size
の値は、親コンテナの幅の変化にのみ適応することを意味します。私は block-size
を使用しようとしましたが、要件を満たさないことがわかりました。私が間違っている場合は訂正してください。
<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>
.o-grid__item {
contain: layout inline-size;
}
これが第一歩です。私たちは .o-grid__item
要素を .c-article
の親コンテナとして定義しました。
次のステップは、コンテナクエリ機能を有効にするために必要なスタイルを追加することです。
.o-grid__item {
contain: layout inline-size;
}
@container (min-width: 400px) {
.c-article {
display: flex;
flex-wrap: wrap;
}
/* 他の CSS.. */
}
@container
は .o-grid__item
要素を指し、min-width: 400px
はその幅(適応)です。私たちはさらに多くのスタイルを追加することもできます。以下のビデオでは、カードコンポーネントが実現できる機能を紹介しています(VPN が必要です):
私たちのビデオのスタイル設定には以下があります:
- デフォルトでは、カードスタイルの外観です。
- 小さなサムネイルを持つ横型カード。
- 大きなサムネイルを持つ横型カード。
- 親コンテナが大きすぎる場合、主画像スタイルが表示され、特徴的な記事であることを示します。
CSS コンテナクエリの使用例について考えてみましょう。
CSS コンテナクエリの使用例#
コンテナクエリと CSS Grid auto-fit
#
特定の状況では、CSS grid レイアウトで auto-fit
を使用すると予期しない結果が生じることがあります。たとえば、コンポーネントが広すぎて、その内容が読みづらくなることがあります。
問題の現象をより直感的に理解するために、ここに CSS grid レイアウトでの auto-fit
と auto-fill
の違いを示すビジュアル効果があります。
注意してください、auto-fit
を使用すると、子項目は利用可能なスペースを覆うように適応しますが、auto-fill
の例では、グリッド子項目は伸びず、自由なスペース(最右側の破線部分)が残ります。
あなたは今、これが CSS コンテナクエリとどのように関連しているのか混乱しているかもしれません。各グリッド子項目はコンテナであり、それが展開すると(つまり、私たちが使用している auto-fit
)、コンポーネントはその基盤の上で変更する必要があります。
<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>
.o-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 1rem;
}
私たちが 4 つの要素を持っているとき、見た目は以下のようになります。
記事の数が減ると、変化が起こります。以下の図は何が起こったかを示しています。記事が少なくなるほど、それらは広くなります。理由は auto-fit
を使用しているためです。最初のものは見た目が良いですが、最後の 2 つ(各行に 2 つ、各行に 1 つ)は見た目が悪くなります。なぜなら、それらは広すぎるからです。
もし各記事コンポーネントが親コンテナの幅に基づいてレイアウトを変更する場合、私たちは auto-fit
の利点を十分に享受できるでしょう。以下は私たちがやるべきことです:
グリッド子項目の幅が 400px を超える場合、記事は横スタイルに切り替わるべきです。
私たちはこうすることができます:
.o-grid__item {
contain: layout inline-size;
}
@container (min-width: 400px) {
.c-article {
display: flex;
flex-wrap: wrap;
}
}
さらに、もし記事がグリッド内で唯一の項目であれば、主画像を使用して表示したいと思います。
.o-grid__item {
contain: 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;
}
}
以上のように、親の幅に応じて適応するコンポーネントができました。そして、それはどんな環境でも機能します。素晴らしいことではありませんか?
CodePen 上のデモを確認してください。
サイドバーとメインコンテンツ#
通常、私たちはコンポーネントを調整して、狭いコンテナ(例えば <aside>
)で表示できるようにする必要があります。
クラシックなケースはニュースレター部分で、幅が狭いときにはスタック表示し、幅が十分なときには横に展開できるようにする必要があります。
上の図で見たように、私たちは 2 つの異なるシナリオに適応するニュースレターコンポーネントを持っています:
- サイドバー部分
- メインエリア
コンテナクエリがなければ、これは実現が難しいでしょう。CSS にバリアントクラス(例えば .newsletter--stacked
)を追加する以外には。
私たちはこれらの子項目を強制的にラップすることができます、フレキシブルレイアウトが十分なスペースを持たないようにすることができますが、それでも不十分です。私は以下のことを行うためにもっとコントロールが必要です:
- 特定の要素を非表示にする。
- ボタンをコンテナに合わせて広げる。
.newsletter-wrapper {
contain: layout inline-size;
}
/* デフォルトスタイル、スタックバージョン */
.newsletter {
/* CSS スタイル */
}
.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;
}
}
ここにビデオがあります。
CodePen 上のデモを確認してください。
ページネーション#
私はページネーションのシナリオがコンテナクエリを使用するのに非常に適していると感じています。最初は「前のページ」と「次のページ」ボタンを持っており、十分なスペースがある場合にすべてのページネーションを表示できます。
以下の図を参照してください。
上記の図の状態を処理するために、まずデフォルトスタイル(スタックボタン)を処理し、その後残りの 2 つの状態を処理する必要があります。
.wrapper {
contain: 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;
}
}
CodePen 上のデモを確認してください。
プロフィールカード#
これは、さまざまなシナリオで使用するのに適した別のケースです。小さな状態は小さなビューポートサイズやサイドバーのようなシナリオに適しており、より大きな状態はより大きな環境で機能します。たとえば、2 列のグリッドに配置することができます。
.p-card-wrapper {
contain: 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;
}
/* 他のスタイル */
}
これにより、単一のメディアクエリを使用せずに、異なるシナリオでコンポーネントがどのように使用されるかを見ることができます。
CodePen 上のデモを確認してください。
フォーム要素#
私はまだフォームのケースを深く掘り下げていませんが、ラベルを横状態からスタック状態に切り替えることを考えています。
.form-item {
contain: layout inline-size;
}
.input-group {
@container (min-width: 350px) {
display: flex;
align-items: center;
gap: 1.5rem;
input {
flex: 1;
}
}
}
以下のデモで自分で試してみてください。CodePen 上のデモを確認してください。
コンポーネントのテスト#
私たちはいくつかのケースで CSS コンテナクエリを使用してみましたが、コンポーネントをどのようにテスト(迅速に検証)するのでしょうか?幸いなことに、コンポーネントの親コンテナ CSS resize
属性を使用してこれを行うことができます。
.parent {
contain: layout inline-size;
resize: horizontal;
overflow: auto;
}
私は Bramus Van Damme の記事からこのテクニックを学びました。
DevTools でコンテナクエリをデバッグするのは簡単ですか?#
現時点ではそうではありません。@container (min-width: value)
のようなものを見ることはできませんが、これは時間の問題だと思います。最終的にはサポートされるでしょう。
代替案を提供することは可能ですか?#
はい!もちろん可能です。ある意味で代替案を提供することは可能です。ここにそれを実現する方法を説明する素晴らしい記事が 2 つあります:
- Container Query Solutions with CSS Grid and Flexbox 著者 Stephanie Eckles
- Container Queries are actually coming 著者 Andy Bell
まとめ#
私は CSS コンテナクエリを学び、ブラウザで使用するのが大好きです。公式にサポートされていないことは知っていますが、今がブラウザで使用する良い時期です。
フロントエンド開発者として、私たちの仕事の一部は、これらの新機能を実現することに取り組んでいる人々にテストと支援を提供することです。私たちが多くテストすればするほど、すべての主要なブラウザがこの機能をサポートするようになったときに、私たちが直面する問題は少なくなります。
お読みいただきありがとうございました。