Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails以外の開発一般

待望のCSSコンテナクエリがやってきた(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。章立ては原文のものから変えています。

参考: CSS Container Queries - CSS: Cascading Style Sheets | MDN

待望のCSSコンテナクエリがやってきた(翻訳)

Webサイトをさまざまなデバイスで表示することは常に課題となっていました。レスポンシブデザインが導入される前は、多くの企業が画面サイズ(特にモバイル)に応じてレイアウトを変更する問題に対処するために、サブドメインmの下(すなわちm.mywebsite.com)に新たなWebサイトを作成する形で対応していました。その後レスポンシブデザインとメディアクエリによって、ビューポートのサイズに応じてさまざまなレイアウトを用いるソリューションが生まれました。

メディアクエリは多くのレイアウト問題を解決しましたが、どんな場合でも理想的というわけにはいきません。コンテナで利用可能な幅に合わせてレイアウトの変更が必要なコンポーネントが、多くのWebサイトで使われていますが、これはメディアクエリだけでは実現できません。実現のためには、クラスを書くか要素をセレクタで指定し、そのコンポーネントがドキュメント構造のどの位置にあるかの確認を強いられます。

何の話だかわからない方もご心配なく。では、この例を見ていくことにしましょう。

あるポートフォリオWebサイトを開発するとします。プロフィール写真と短い自己紹介を1個のProfileコンポーネントに収めたいと思います。さらに、このコンポーネントを職歴ページやブログページなどを含む全ページのサイドバーにも表示したいと思います。ただし、自分自身のabout-meページでは、メインエリアにProfileコンポーネントの他に追加の詳細も表示したいとします。このメインエリアのスペースは、サイドバーよりも大きく取ってあります。つまり、利用可能なスペースに応じてProfileコンポーネントの調整が必要になるわけです。

さて、ここで問題になるのは、Profileコンポーネントがサイドバーにあるときは小さめに、メインコンテンツにある場合は大きめに表示することです。

最初に思いつくのは、CSSを2バージョン作成してクラスで使い分けることです。メインエリアの場合はProfileコンポーネントにlargeクラスを追加します。

  <aside>
    <div class="aboutme">
      <header>
        <img class="avatar" src="./profile_pic.png" alt="Chetan Gawai profile">
        <h1>Chetan Gawai</h1>
      </header>
      <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry....</p>
    </div>
  </aside>
  <main>
    <div class="aboutme large">
      <header>
        <img class="avatar" src="./profile_pic.png" alt="Chetan Gawai profile">
        <h1>Chetan Gawai</h1>
      </header>
      <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry....</p>
    </div>
  </main>
 /* デフォルトのスタイル */
.avatar {
  width: 4rem;
  height: 4rem;
}
.aboutme header {
  display: flex;
  align-items: center;
  gap: 1rem;
}
h1 {
  font-size: 23px;
}
/* largeスタイル */
.large.aboutme {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.large .avatar {
  width: 10rem;
  height: 10rem;
}
.large header {
  display: block;
}
.large h1 {
  font-size: 46px;
}

これでOKでしょうか?

いいえ、まだ問題が残っています。画面をリサイズした後でも大きい方のコンポーネントが表示されたままになっているので、「スペースが十分ある場合にのみ」大きい方を表示するようにしたいと思います。

どうやったら修正できるでしょうか?

その答えはメディアクエリということになりますが、クラスの追加や削除にはJavaScriptが必要になるため、CSSが複雑になってしまいます。そういうわけでこの方法は使わないことにします。

では別の方法を考えてみましょう。

要素が置かれている場所(サイドバーやメイン)に応じて要素を絞り込むことにするとします。しかしこの方法にも独自の問題があります。状況によっては、利用可能なスペースがあるなら画面が小さい場合でも大きい方を表示したいこともあります。メディアクエリは、ビューポートの幅に縛られてしまうので使えません。

Profileコンポーネントがその親要素の幅に応じてスタイルを変更するようになったらどうでしょう?

これができれば素晴らしいことですよね。

メディアクエリを使えばこれを実現できますが、相当頑張らないといけなくなります。

このアイデアをもっとシンプルに実現できないものでしょうか?

そう、コンテナクエリという強い味方がいるのです。

コンテナクエリについて

CSSに新たに追加されたコンテナクエリは、レスポンシブ対応に新鮮な手法を提供します。コンテナクエリは、(ビューポートのサイズではなく)その親コンテナや親要素との関係で要素の変更を指定するので、開発者がレスポンシブ対応をコンポーネントレベルで行うことを支援します。

構文

コンテナクエリの構文はメディアクエリの構文と似ています。

コンテナクエリには、3つのプロパティと1つのルールが加わります。

1. container-typeプロパティ: 要素をクエリコンテナとして定義します。これにより、子コンポーネントがその要素のサイズ、レイアウト、スタイル、ステートの側面についてクエリできるようになります。

container-typeプロパティには以下の値を指定できます。

size
ブロック軸とインライン軸のサイズクエリで用いるクエリコンテナを設定します。その要素にレイアウト、スタイル、サイズの封じ込め(containment)1を適用します。
inline-size
コンテナのインライン軸のサイズクエリで用いるクエリコンテナを設定します。その要素にレイアウト、スタイル、サイズの封じ込めを適用します。
normal
その要素が、ブロック軸とインライン軸のどのサイズクエリについてもクエリコンテナではないことを表します。

2. container-nameプロパティ: フィルタリングで用いるクエリコンテナ名を設定します。

3. containerプロパティ: container-typecontainer-nameをまとめて設定するショートハンドです。

コンテナをターゲットにするには、@containerを用いて、最も近くにある封じ込めコンテキストをターゲットとするルールを指定します。

コンテナクエリを使う

先ほどのコードを変更して、コンテナクエリを使ってみましょう。

  <aside>
    <div class="main-wrapper">
      <div class="aboutme">
        ...
      </div>
    </div>
  </aside>
  <main>
    <div class="main-wrapper">
      <div class="aboutme">
        ...
      </div>
    </div>
  </main>
/* デフォルトのスタイル */
.main-wrapper {
  container-type: inline-size;
}
.avatar {
  width: 4rem;
  height: 4rem;
}
.aboutme header {
  display: flex;
  align-items: center;
  gap: 1rem;
}
h1 {
  font-size: 23px;
}
/* largeスタイル */
@container (min-width: 800px) {
  .aboutme {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .avatar {
    width: 10rem;
    height: 10rem;
  }
  header {
    display: block;
  }
  h1 {
    font-size: 46px;
  }
}

asidemainの内側にあるaboutmeクラスのdivを、main-wrapperクラスのコンテナでラップし、メインエリアからlargeクラスを削除しました。

それに続くCSSによって、このコンテナに後でクエリをかけたくなる可能性があることをブラウザが認識します。

以下のCSSは、インライン軸にのみ"封じ込め"を持たせたコンテナを作成します。このコンテンツは、ブロック軸で必要なサイズまで広がるようになります。

  .main-wrapper {
    container-type: inline-size;
  }

コンテナクエリは@containerで作成されます。これは、自分に最も近い"封じ込め"コンテキストにクエリを発行します。

以下のコードは、メインコンテンツの幅が800pxより大きい場合に大きい方のProfileコンポーネントを表示します。

  @container (min-width: 800px) {
    ....
  }

やりました!私たちが欲しかったのはまさにこれです。この同じコンポーネントをさまざまな場所に配置すれば、親コンポーネントの幅に応じて振る舞うようになります。

ブラウザのサポート

コンテナクエリはChrome 105でサポートされており、Safari 16でも間もなくサポートされる予定です。


Credits: CanIUseより

訳注

その後Safari 16がリリースされてコンテナクエリが使えるようになりました。Edgeはバージョン105で対応済みです。また、Firefox 110でもリリース予定が発表されました。

参考: CSSコンテナクエリがFirefoxでリリース予定!全モダンブラウザで使用可能へ。

追記: その後Firefox 110がリリースされ、主要ブラウザがコンテナクエリに対応しました。

参考: Firefox 110.0, See All New Features, Updates and Fixes

本記事が皆さんのお役に立ちますように。

Happy Coding!

関連記事

CSS: フィーチャークエリを理解する(翻訳)

CSS GridがBootstrapよりレイアウト作成に向いている理由(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。