Header image

クラッソーネの開発者がエンジニアリングに関することもそうでないことも綴っています!

コンテナクエリと React と Chakra UI

コンテナクエリと React と Chakra UI

こんにちは!プロダクト開発部の服部です。

最近、実家の近所にお気に入りのパン屋を見つけて足繁く通っています。
ハード系のパンも美味しいですが、モッチモチのパンも美味しいですよね。

からし明太子を使った“自家製明太子バター”と、クリームチーズをソフトフランス生地で包んでいる「明太フォンデュ」が美味しすぎて病みつきになっています。

コンテナクエリとは?

CSS のコンテナクエリ(@container)は指定したコンテナサイズに合わせてブレイクポイントを切り替えられる機能です。

2023 年 2 月、Firefox がコンテナクエリに対応したことにより、全ブラウザにてコンテナクエリが利用できるようになりました!

コンテナクエリ vs メディアクエリ

コンテナクエリ vs メディアクエリ

コンテナクエリ

コンテナクエリは、その要素自体の幅や高さに基づいてスタイルを適用するため、同じページ内の異なるコンテナで異なるスタイルを適用することが可能になります。

各コンポーネントが自身のレイアウトを制御できるため、コンポーネントを異なるコンテキストで再利用する場合に非常に便利であり、デザインの柔軟性が大幅に向上します。

コンテナクエリ
※ ↑ 要素の親コンテナのサイズに応じてスタイルを適用した例。同じページ内でも、container-typeで指定したコンテナサイズ合わせてブレイクポイントを切り替えられている。

メディアクエリ

メディアクエリ(@media)は、全体のビューポート(ブラウザウィンドウ)サイズ基づいてスタイルを適用します。ページ全体で一貫したスタイルを適用するのは容易ですが、個々の要素やコンポーネントに対して異なるスタイルを適用するのは困難です。

メディアクエリ
※ ↑ viewport のサイズに応じてスタイルを適用した例。同じページ内なので、同じ viewport サイズを参照している。

コンテナクエリと React の相性が良いワケ

  1. コンポーネントベースのライブラリであり、再利用可能な部品として設計されていること: コンテナクエリもまたそれぞれのコンポーネントが自身のレイアウトを管理することを可能にするため、この設計思想に非常に相性が良いです。

  2. 再利用性が高いこと: React コンポーネントは再利用可能であるため、異なる親要素(コンテナ)に対して異なるスタイルを適用したい場合が多々あります。コンテナクエリは、コンポーネントが親要素のサイズに基づいてレイアウトを動的に調整することを可能にし、その再利用性をさらに高めます。

たとえば、あるコンポーネントが様々な親要素の中で使用され、それぞれの親要素のサイズに応じてレイアウトを変更する必要がある場合、コンテナクエリを用いることでそのコンポーネントのスタイルを動的に変更することができます。

コンテナクエリの基本的な使い方

container-type プロパティを使用した基本的なコンテナクエリの例を示します。

コンテナクエリにおいて、container-type プロパティは要素をコンテナとして扱うことをブラウザに指示します。

コンテナとして要素を定義する

container-type プロパティを使用して、要素をコンテナとして定義します。このプロパティの値には、inline-size(要素横幅のコンテナサイズ)または size(インライン方向・ブロック方向のコンテナサイズ) を指定できます。

html
<div class="my-container">
  <div class="content">
    Hello, world!
  </div>
</div>

ここでは、my-containerクラスを持つ div 要素がコンテナとして機能し、その中に content クラスを持つ子要素が配置されています。

css
/* container-type プロパティを使用して、.my-container要素をコンテナとして定義 */
.my-container {
  container-type: inline-size;
}

コンテナクエリを適用する

@container ルールを使用して、コンテナのサイズに応じてスタイルを適用します。

.my-container {
  container-type: inline-size;
}

@container (min-width: 500px) {
  .content {
    /* styles for container wider than 500px */
    background-color: blue;
    color: white;
  }
}

@container (min-width: 800px) {
  .content {
    /* styles for container wider than 800px */
    background-color: green;
    color: black;
  }
}

CSS では、まず container-type プロパティを使用して my-container クラスの要素をコンテナとして定義しています。その後、@container ルールを使用して、コンテナの幅が 500px 以上と 800px 以上の場合にそれぞれ異なるスタイルを適用しています。

この例では、コンテナの幅が 500px 以上の場合、content クラスの要素の背景色は青、文字色は白になります。また、コンテナの幅が 800px 以上の場合、背景色は緑、文字色は黒に変更されます。

サンプルコードはこちら

このように、コンテナクエリを使用すると、簡単に要素の親コンテナのサイズに応じてスタイルを適用することができます。

メディアクエリを適用すると。。。

@media メディアクエリだと次のようになります。コンテナクエリのサイズの基準がcontainer-typeプロパティーで指定したコンテナであるのに対して、メディアクエリのサイズの基準は viewport です。

css
@media (min-width: 500px) {
  .content {
    /* styles for viewport wider than 500px */
    background-color: blue;
    color: white;
  }
}

@media (min-width: 800px) {
  .content {
    /* styles for viewport wider than 800px */
    background-color: green;
    color: black;
  }
}

おまけ Chakra UI でコンテナクエリを使うには?

私が関わるプロジェクトのひとつに、Chakra UI(React ui コンポーネントライブラリ) を採用した Web アプリ開発があります。

幅の異なるコンテナ配下で UI コンポーネントを再利用するにあたり、コンテナクエリを利用したい場面がありました。

しかし、現在のところ(2023 年 5 月時点)、container-typeプロパティーは、Chakra UI:Style Propsに記載されていないことから、直接コンテナクエリをサポートしていないようです。

一方で sx プロパティーにメディアクエリ(公式サイトでは@media printを記述できる)とあったので、もしかしたらsxプロパティーを利用してコンテナクエリ実装できるのでは?と考え、早速試してみました。

jsx
<Box
  sx={{
    '@media print': {
      display: 'none',
    },
  }}
>
  This text won't be shown when printing this page.
</Box>

参考:Chakra UI:The sx Prop

Chakra UI でコンテナクエリを適用してみる

  1. 基準となるコンテナを指定する
  2. sx プロパティーを使って、@containerを適用してみる
jsx
<Box
  sx={{
  // 基準となるコンテナを指定する
   containerType: 'inline-size',
  }}
>
  <Box
    textAlign='center'
    sx={{
      "@container (min-width: 500px)": {
      /* styles for container wider than 500px */
      backgroundColor: 'blue',
      color: white;
      },
      "@container (min-width: 800px)": {
      /* styles for container wider than 800px */
      backgroundColor: 'green',
      color: 'black'
      },
    }}
  >
    Hello, world!
  </Box>
</Box>

無事適用できました!

サンプルコードはこちら

コンテナクエリを使いこなして、ますます楽しい React ライフを!

おわりに

クラッソーネでは、プロダクトとチームの双方をより良く改善していけるエンジニアを募集中です!

https://www.crassone.co.jp/recruit/engineer/