web制作

CSSコンテナクエリがすごい!cqwで実現する真のコンポーネント指向レスポンシブデザイン

Web開発者の皆さん、vwを使うたびにclamp()を書くのに疲れていませんか?CSSコンテナクエリ(Container Queries)、特にcqwユニットを使えば、親にmax-widthを設定するだけで、子要素が大きくなりすぎるのを防げます。今回は、コンテナクエリの魅力と実践的な使い方を徹底解説します!

コンテナクエリとは?なぜすごいのか

コンテナクエリは、要素のサイズを親コンテナの幅に基づいて調整できる革新的なCSS機能です。

従来のvwの問題点:clampが必須で面倒…

vw(Viewport Width)はブラウザウィンドウ全体の幅を基準にします。そのため、大きな画面で文字が巨大になりすぎないように、また小さな画面で読めなくならないように、毎回clamp()で最小値と最大値を設定する必要があります。

問題点:

  • 全てのプロパティにclamp()を書く必要がある
  • 最小値・最大値の計算が面倒
  • コードが冗長で読みにくい
  • 保守性が低い

cqwの革命的アプローチ:親のmax-widthだけでOK!

cqw(Container Query Width)は親コンテナの幅を基準にします。親にmax-widthを設定すれば、子要素は自動的にその範囲内でスケールします。

メリット:

  • clamp()を書く必要がない
  • 親のmax-widthだけで最大サイズを制限
  • コードがシンプルで読みやすい
  • 保守性が高い

具体的な実装方法

まずはシンプルなHTML構造から始めましょう。商品カードを例に、vwcqwの違いを見ていきます。

基本のHTML構造

さあ、ここからは実際にコードを書いてみていきます。

実際にvwとcqwのちがいをみていきましょう。

Copy<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>コンテナクエリ デモ</title>
</head>
<body>
  <!-- vw使用例 -->
  <div class="vw-card">
    <div class="card-category">カテゴリ</div>
    <h2 class="card-title">vwアプローチ</h2>
    <div class="card-price">¥12,800</div>
    <p class="card-description">これはvwを使った商品カードです。</p>
  </div>

  <!-- cqw使用例 -->
  <div class="cqw-wrapper">
    <div class="cqw-card">
      <div class="card-category">カテゴリ</div>
      <h2 class="card-title">cqwアプローチ</h2>
      <div class="card-price">¥12,800</div>
      <p class="card-description">これはcqwを使った商品カードです。</p>
    </div>
  </div>
</body>
</html>

vwアプローチ: clampが必須で面倒…

何度も行ってきましたがvwはビューポート幅に依存しますよね。

コンテンツ幅は大体980pxとか1200pxと決めていてもPCで見ていると大体1980pxまでいきますよね。

そんな時font-sizeをvwにするとどんどん大きくなっていきます。

そこで便利なのがclamp.こいつのおかげで最小値と最大値を決めることができる優れもの。

だけど、毎回最大値を書かないといけない。これ地味に面倒なんだよな!

Copy/* vwカードのスタイル */
.vw-card {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  padding: 30px;
  border-radius: 16px;
  color: white;
}

/* 毎回clampを書く必要がある... */
.vw-card .card-category {
  font-size: clamp(14px, 3vw, 24px); /* 面倒... */
}

.vw-card .card-title {
  font-size: clamp(24px, 6vw, 48px); /* また書く... */
}

.vw-card .card-price {
  font-size: clamp(36px, 10vw, 72px); /* さらに書く... */
}

.vw-card .card-description {
  font-size: clamp(12px, 2.5vw, 16px); /* 毎回計算が必要... */
}

cqwアプローチ: 親のmax-widthだけでOK!

じゃあ、cqwだとどうなの?

はい、朗報です。

コンテナサイズに依存するので指定したコンテナの最大幅だけ決めておけばビューポート幅に関係ない。

つまり、cqwだけでほぼ完結するのです。やったー!

Copy/* cqwカードのラッパー */
.cqw-wrapper {
  container-type: inline-size; /* コンテナとして定義 */
  max-width: 600px; /* これだけで子要素の最大サイズを制限! */
}

/* cqwカードのスタイル */
.cqw-card {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  padding: 30px;
  border-radius: 16px;
  color: white;
}

/* clamp不要!シンプル! */
.cqw-card .card-category {
  font-size: 3cqw; /* シンプル! */
}

.cqw-card .card-title {
  font-size: 6.5cqw; /* 楽! */
}

.cqw-card .card-price {
  font-size: 15cqw; /* 簡潔! */
}

.cqw-card .card-description {
  font-size: 2.5cqw; /* 計算不要! */
}

コンテナに名前をつけると更に明確になる!

ここからが重要なポイントです。複雑なレイアウトでは、コンテナがネストされることがよくあります。そんな時、「親要素」や「先祖要素」と漠然と考えるよりも、コンテナに名前をつけることで、どのコンテナを対象にしているか明確になります。

名前なしの場合:どのコンテナ?

コンテナ便利っていうことで、どんどん増やしていくとどのコンテナに依存しているのかわからなくなります。

特にネストされているとどれがどれ?ってなります。

こいつは困った。

Copy<div class="outer">
  <div class="middle">
    <div class="inner">
      <p class="text">このテキストはどのコンテナを基準にしている?</p>
    </div>
  </div>
</div>
Copy.outer {
  container-type: inline-size;
  max-width: 1200px;
}

.middle {
  container-type: inline-size;
  max-width: 800px;
}

.inner {
  container-type: inline-size;
  max-width: 400px;
}

.text {
  /* どのコンテナを基準にしている?
     最も近い.innerを基準にするが、パッと見て分かりにくい */
  font-size: 5cqw;
}

上記コードの場合、.textは最も近い先祖コンテナ(.inner)を基準にしますが、コードを読むだけでは直感的に分かりにくいですよね。

名前ありの場合:一目瞭然!

そこで、コンテナに名前をつけてわかりやすくしようっていう話です。

Copy<div class="page-container">
  <div class="sidebar-container">
    <div class="card-container">
      <p class="text">このテキストはsidebarコンテナを基準にしている!</p>
    </div>
  </div>
</div>
Copy/* コンテナに名前をつける */
.page-container {
  container-type: inline-size;
  container-name: page; /* 名前: page */
  max-width: 1200px;
}

.sidebar-container {
  container-type: inline-size;
  container-name: sidebar; /* 名前: sidebar */
  max-width: 400px;
}

.card-container {
  container-type: inline-size;
  container-name: card; /* 名前: card */
  max-width: 300px;
}

/* 特定のコンテナを明示的に指定! */
@container sidebar (min-width: 300px) {
  .text {
    font-size: 5cqw; /* sidebarコンテナを基準にしているのが明確! */
    color: blue;
  }
}

@container card (min-width: 200px) {
  .text {
    font-size: 8cqw; /* cardコンテナを基準にしているのも明確! */
    color: red;
  }
}

メリット:

  • どのコンテナを対象にしているか一目瞭然
  • 「親要素」「先祖要素」という曖昧な表現が不要
  • コードの可読性が大幅に向上
  • 保守性が高く、他の開発者も理解しやすい

実践例:サイドバーとメインコンテンツ

Copy<div class="layout">
  <!-- サイドバー -->
  <aside class="sidebar">
    <div class="widget">
      <h3 class="widget-title">人気記事</h3>
      <p class="widget-text">記事の説明文</p>
    </div>
  </aside>

  <!-- メインコンテンツ -->
  <main class="main">
    <article class="article">
      <h2 class="article-title">記事タイトル</h2>
      <p class="article-text">記事本文</p>
    </article>
  </main>
</div>
Copy/* サイドバーコンテナに名前をつける */
.sidebar {
  container-type: inline-size;
  container-name: sidebar; /* 名前: sidebar */
  max-width: 300px;
}

/* メインコンテナに名前をつける */
.main {
  container-type: inline-size;
  container-name: main; /* 名前: main */
  max-width: 900px;
}

/* sidebarコンテナ専用のスタイル */
@container sidebar (min-width: 250px) {
  .widget-title {
    font-size: 6cqw; /* sidebarの幅を基準 */
  }
  
  .widget-text {
    font-size: 4cqw;
  }
}

/* mainコンテナ専用のスタイル */
@container main (min-width: 600px) {
  .article-title {
    font-size: 5cqw; /* mainの幅を基準 */
  }
  
  .article-text {
    font-size: 2.5cqw;
  }
}

この方法なら、「sidebarコンテナを基準にしている」「mainコンテナを基準にしている」ということが明確になります!

さらに複雑な例:ダッシュボード

Copy<div class="dashboard">
  <div class="dashboard-grid">
    <!-- 小さいウィジェット -->
    <div class="widget-small">
      <div class="widget-content">
        <span class="value">128</span>
        <span class="label">訪問者</span>
      </div>
    </div>

    <!-- 大きいウィジェット -->
    <div class="widget-large">
      <div class="widget-content">
        <span class="value">¥1,280,000</span>
        <span class="label">売上</span>
      </div>
    </div>
  </div>
</div>
Copy/* ダッシュボードコンテナ */
.dashboard {
  container-type: inline-size;
  container-name: dashboard;
  max-width: 1400px;
}

/* 小さいウィジェットコンテナ */
.widget-small {
  container-type: inline-size;
  container-name: widget-small; /* 名前: widget-small */
  max-width: 300px;
}

/* 大きいウィジェットコンテナ */
.widget-large {
  container-type: inline-size;
  container-name: widget-large; /* 名前: widget-large */
  max-width: 600px;
}

/* widget-small専用のスタイル */
@container widget-small (min-width: 200px) {
  .value {
    font-size: 15cqw; /* widget-smallの幅を基準 */
  }
  
  .label {
    font-size: 4cqw;
  }
}

/* widget-large専用のスタイル */
@container widget-large (min-width: 400px) {
  .value {
    font-size: 10cqw; /* widget-largeの幅を基準 */
  }
  
  .label {
    font-size: 3cqw;
  }
}

名前をつけることで:

  • 小さいウィジェットと大きいウィジェットで異なるスタイルを適用できる
  • どのコンテナを対象にしているか一目で分かる
  • コードの意図が明確になる

vw vs cqw 実例で見る違い

vw: 毎回clampを書く必要がある

大事なのでもう一度繰り返しますw。

実際にいくつかの例を載せましたので、コピペして違いが実感してください。

Copy/* ❌ 面倒なvwアプローチ */
.vw-card {
  /* 親にmax-widthを設定しても意味がない */
  max-width: 600px;
}

.vw-card .card-category {
  /* ビューポート基準なので、clampで制限が必須 */
  font-size: clamp(14px, 3vw, 24px);
}

.vw-card .card-title {
  /* また書く... */
  font-size: clamp(24px, 6vw, 48px);
}

.vw-card .card-price {
  /* さらに書く... */
  font-size: clamp(36px, 10vw, 72px);
}

.vw-card .card-description {
  /* 毎回計算が必要... */
  font-size: clamp(12px, 2.5vw, 16px);
}

cqw: 親のmax-widthだけでOK

Copy/* シンプルなcqwアプローチ */
.cqw-wrapper {
  container-type: inline-size;
  container-name: product-card; /* 名前をつけると更に明確! */
  max-width: 600px; /* これだけ! */
}

.cqw-card .card-category {
  /* clamp不要! */
  font-size: 3cqw;
}

.cqw-card .card-title {
  font-size: 6.5cqw;
}

.cqw-card .card-price {
  font-size: 15cqw;
}

.cqw-card .card-description {
  font-size: 2.5cqw;
}

/* 名前付きコンテナクエリも使える */
@container product-card (min-width: 400px) {
  .cqw-card {
    padding: 40px; /* product-cardが400px以上の時だけ適用 */
  }
}

実践的な使用例

例1: カードコンポーネント

Copy<div class="product-card-wrapper">
  <div class="product-card">
    <div class="label">新着</div>
    <h3 class="heading">新商品</h3>
    <p class="body">詳細な説明文がここに入ります。</p>
  </div>
</div>
Copy.product-card-wrapper {
  container-type: inline-size;
  container-name: product-card; /* 名前: product-card */
  max-width: 500px;
}

.product-card .label {
  font-size: 2cqw;
}

.product-card .heading {
  font-size: 6cqw;
}

.product-card .body {
  font-size: 3cqw;
}

/* 名前付きコンテナクエリ */
@container product-card (min-width: 350px) {
  .product-card {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  }
}

例2: グリッドレイアウト

Copy<div class="grid">
  <div class="grid-item">
    <div class="grid-card">
      <h3 class="grid-title">デザイン</h3>
      <p class="grid-text">説明文</p>
    </div>
  </div>

  <div class="grid-item">
    <div class="grid-card">
      <h3 class="grid-title">エンジニアリング</h3>
      <p class="grid-text">説明文</p>
    </div>
  </div>
</div>
Copy.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

.grid-item {
  container-type: inline-size;
  container-name: grid-item; /* 名前: grid-item */
  max-width: 400px;
}

.grid-card {
  padding: 3cqw;
  border-radius: 2cqw;
}

.grid-title {
  font-size: 8cqw;
}

.grid-text {
  font-size: 4cqw;
}

/* 名前付きコンテナクエリ */
@container grid-item (min-width: 300px) {
  .grid-card {
    box-shadow: 0 10px 30px rgba(0,0,0,0.2);
  }
}

注意点とよくある落とし穴

1. container-typeの定義を忘れない

Copy/* これではcqwが機能しない */
.parent {
  max-width: 600px; /* container-typeがない! */
}

.child {
  font-size: 5cqw; /* 機能しない */
}

/* 正しい実装 */
.parent {
  container-type: inline-size; /* 必須! */
  container-name: my-container; /* 名前をつけると更に良い */
  max-width: 600px;
}

.child {
  font-size: 5cqw; /* 完璧! */
}

2. ネストされたコンテナは名前で明確に

Copy<div class="outer-container">
  <div class="inner-container">
    <div class="text">どのコンテナを基準にする?</div>
  </div>
</div>
Copy/* 名前をつけて明確に */
.outer-container {
  container-type: inline-size;
  container-name: outer; /* 名前: outer */
  max-width: 1000px;
}

.inner-container {
  container-type: inline-size;
  container-name: inner; /* 名前: inner */
  max-width: 500px;
}

/* デフォルトでは最も近いコンテナ(inner)を基準 */
.text {
  font-size: 5cqw;
}

/* 特定のコンテナを明示的に指定できる */
@container outer (min-width: 800px) {
  .text {
    color: blue; /* outerコンテナが800px以上の時 */
  }
}

@container inner (min-width: 400px) {
  .text {
    color: red; /* innerコンテナが400px以上の時 */
  }
}

名前をつけることで、どのコンテナを対象にしているか一目瞭然!

3. ブラウザサポートの確認

モダンブラウザでは広くサポートされていますが、古いブラウザ向けにフォールバックを用意しましょう:

Copy.text {
  font-size: 16px; /* フォールバック */
  font-size: 5cqw; /* サポートされていれば上書き */
}

コード量の比較

vwアプローチ(冗長)

Copy/* 30行以上のコード */
.card .label {
  font-size: clamp(14px, 3vw, 24px);
}

.card .title {
  font-size: clamp(24px, 6vw, 48px);
}

.card .value {
  font-size: clamp(36px, 10vw, 72px);
}

.card .description {
  font-size: clamp(12px, 2.5vw, 16px);
}

.card {
  padding: clamp(20px, 3vw, 40px);
  margin-bottom: clamp(10px, 2vw, 20px);
  border-radius: clamp(8px, 1.5vw, 16px);
}

cqwアプローチ(シンプル)

Copy/* 15行のシンプルなコード */
.card-wrapper {
  container-type: inline-size;
  container-name: card; /* 名前をつけると更に明確 */
  max-width: 600px;
}

.card .label {
  font-size: 3cqw;
}

.card .title {
  font-size: 6.5cqw;
}

.card .value {
  font-size: 15cqw;
}

.card .description {
  font-size: 2.5cqw;
}

.card {
  padding: 3cqw;
  margin-bottom: 2cqw;
  border-radius: 1.5cqw;
}

結果: コードの見通しが良い

cqwが最適な使い所

使うべきケース

実際に使えそうなケースを書き出してみました。

特にコンポーネントでの利用価値は大!

  • 再利用可能なカードコンポーネント: どこでも再利用可能
  • グリッドアイテム: グリッドレイアウト
  • デザインシステムのコンポーネント: 一度作れば、どこでも再利用可能
  • ネストされた複雑なレイアウト: 名前をつけることで明確に管理

注意が必要なケース

  • container クエリーをそもそもつけ忘れに注意
  • グローバルなタイポグラフィ: サイト全体の基本フォントサイズにはremが適切
  • 固定幅コンテナ: サイズが固定ならpxで十分
  • アクセシビリティが重要な場面: ユーザーのフォントサイズ設定を尊重する必要がある場合はremを併用

まとめ:コンテナクエリで次世代のレスポンシブデザインへ

CSSコンテナクエリ、特にcqwユニットは、真のコンポーネント指向デザインを実現する強力なツールです。

メリットのまとめ

  • 親のmax-widthだけで子要素のサイズを制限できる
  • clamp()を書く必要がない(これが最大の利点!)
  • コンテナに名前をつけることで、対象が明確になる
  • 「親要素」「先祖要素」という曖昧な表現が不要
  • コードがシンプルで読みやすい
  • 保守性が高く、変更に強い
  • コンポーネントが独立して適切にスケールする
  • メディアクエリの複雑さを大幅に削減
  • 再利用性の高いコンポーネントを構築できる
  • デザインシステムとの相性が抜群

container-nameのメリット

Copy/* 名前なし: どのコンテナか分かりにくい */
.parent {
  container-type: inline-size;
}

/* 名前あり: 一目瞭然! */
.sidebar {
  container-type: inline-size;
  container-name: sidebar; /* これで明確! */
}

@container sidebar (min-width: 300px) {
  /* sidebarコンテナを対象にしているのが明確 */
  .widget {
    font-size: 5cqw;
  }
}

vwとcqwの使い分け

用途推奨ユニット理由
コンポーネント内部のスタイリングcqw親のmax-widthだけでサイズ制限可能
ページ全体のレイアウトvw(clamp必須)ビューポート全体を基準にする必要がある場合
固定サイズの要素pxサイズを固定したい場合
アクセシビリティ重視remユーザー設定を尊重

実際のデモ

今回作成したデモページでは、以下の点を確認できます:

  1. vwとcqwの視覚的な比較: 視覚的な違いが一目瞭然
  2. コードの簡潔さの違い: シンタックスハイライト付きで比較
  3. グリッドレイアウトでの実例: ブラウザをリサイズして、cqwの威力を体感
  4. 実際に動くコード: すぐにプロジェクトに応用可能

ブラウザをリサイズして、cqwがどれだけ柔軟にスケールするか試してみてください。特にグリッドアイテムは、各カードが独立して完璧にスケールする様子が確認できます。

今日から始める cqw

今日からcqwを使って、より柔軟で保守性の高いCSSを書いてみませんか?

始め方は簡単:

  1. 親要素にcontainer-type: inline-sizeを追加
  2. 親要素にcontainer-nameで名前をつける(対象が明確になる!)
  3. 親要素にmax-widthを設定(デカくなりすぎない!)
  4. 子要素でcqwを使う(clamp不要!)

特にcontainer-nameを使うことで:

  • コードの可読性が劇的に向上
  • 複雑なネストでも対象が明確
  • 現代のcssにちょうどいい

ぜひ使ってみましょう。

-web制作