CSS 入門

Flexbox:flex-grow, flex-shrink, flex-basis

flex-growflex-shrink、そして flex-basis。これら3つのプロパティは、フレックスアイテムがコンテナ内でどのようにサイズを調整し、利用可能なスペースをどう分配するかを精密にコントロールするための鍵となります。

このセクションでは、各プロパティを個別に解剖し、それらを組み合わせた際の効果を紐解いていきます。ダイナミックで自律的なUI(ユーザーインターフェース)を構築する能力を養いましょう。

1. flex-grow(伸長係数)を完全にマスターする

flex-grow プロパティは、コンテナ内に余白がある場合、特定のアイテムが他のアイテムに対してどれくらいの比率で拡大するかを決定します。これは単位のない数値を受け取り、スペース分配の比率係数として機能します。

1.1 flex-grow の基本例

次のような3つのアイテムを持つコンテナを想定します。

<div class="container">
  <div class="item item-1">アイテム 1</div>
  <div class="item item-2">アイテム 2</div>
  <div class="item item-3">アイテム 3</div>
</div>
.container {
  display: flex;
  width: 500px; /* デモ用にコンテナ幅を固定 */
  border: 1px solid black;
}

.item {
  background-color: lightblue;
  margin: 5px;
  text-align: center;
  padding: 10px;
}

/* すべてのアイテムを同じ比率で拡大 */
.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 1; }
.item-3 { flex-grow: 1; }

このシナリオでは、3つのアイテムすべてに flex-grow: 1 が設定されています。これは、コンテナ内のすべての余剰スペースが均等に分配されることを意味します。各アイテムの初期幅の合計がコンテナ幅より小さい場合、すべてのアイテムが等しく拡大し、スペースを埋め尽くします。

1.2 異なる flex-grow 値の使用

では、比率を変えてみましょう。

.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 2; }
.item-3 { flex-grow: 1; }

ここでは、アイテム 2 の伸長係数が 2 で、アイテム 1 と 3 は 1 です。つまり、アイテム 2 が獲得する余白は、アイテム 1 や 3 の2倍になります。

計算ロジック: 仮にコンテナに 100px の余白があるとします。すべての flex-grow の合計は (1 + 2 + 1 = 4) です。アイテム 2 は総余白の 2/4(つまり 50px)を獲得し、アイテム 1 と 3 はそれぞれ 1/4(25px)ずつ獲得します。

1.3 flex-grow を 0 に設定した場合

flex-grow0 に設定すると、どれだけ余白があってもそのアイテムは拡大しません。flex-basis で指定されたサイズ(flex-basisauto ならコンテンツ自体のサイズ)を厳格に維持します。

.item-1 { flex-grow: 0; } /* 拡大しない */
.item-2 { flex-grow: 1; }
.item-3 { flex-grow: 1; }

この場合、アイテム 1 は初期サイズのまま変化せず、残りのすべての余白をアイテム 2 と 3 で等分することになります。

プロのヒント:flex-grow が効かない時

flex-grow は、コンテナ内に「余白」がある場合にのみ機能します。アイテムがすでにコンテナを埋め尽くしている(あるいは溢れている)状態では、それ以上膨張することはありません。

2. flex-shrink(収縮係数)のメカニズム

flex-shrink プロパティは、コンテナのスペースが不足した際に、特定のアイテムが他のアイテムに対してどれくらいの比率で縮小するかを指定します。これも単位のない数値で比率を表します。

2.1 flex-shrink の基本例

同じHTML構造で、今度はコンテナを狭くしてみます。

.container {
  display: flex;
  width: 300px; /* 小さなコンテナ幅。溢れが発生する原因 */
  border: 1px solid black;
}

.item {
  background-color: lightblue;
  margin: 5px;
  text-align: center;
  padding: 10px;
  width: 150px; /* 各アイテムの初期幅(計 450px) */
}

.item-1 { flex-shrink: 1; }
.item-2 { flex-shrink: 1; }
.item-3 { flex-shrink: 1; }

各アイテムの初期幅は 150px(合計 450px)ですが、コンテナは 300px しかありません。ブラウザのデフォルト挙動(flex-shrink: 1)により、アイテムは 300px に収まるように等比で縮小されます。

2.2 異なる flex-shrink 値の使用

縮小比率を調整してみます。

.item-1 { flex-shrink: 1; }
.item-2 { flex-shrink: 2; } /* アイテム2がより多く縮む */
.item-3 { flex-shrink: 1; }

このシーンでは、アイテム 2 はアイテム 1 や 3 の2倍の強度で縮小されます。

2.3 flex-shrink を 0 に設定した場合

flex-shrink0 に設定すると、どれだけスペースが不足してもそのアイテムは縮小を拒否します。これにより、アイテムがコンテナから溢れ出す(オーバーフロー)原因になることがよくあります。

.item-1 { flex-shrink: 0; } /* 縮小を拒否 */
.item-2 { flex-shrink: 1; }
.item-3 { flex-shrink: 1; }

2.4 アドバンス:収縮量の正確な計算方法は?

ブラウザが各アイテムを何ピクセル縮小させるかのロジックは、実は単純な比率ではありません。以下のステップで計算されます:

  1. 総溢れ量の算出: (アイテムの総幅 - コンテナ幅) を計算。
  2. 重み付き収縮因子の算出: 各アイテムの (flex-shrink 値 × flex-basis 値) を計算。
  3. 総重みの算出: すべてのアイテムの重み付き収縮因子を合計。
  4. 個別の収縮比率: (個別の重み付き因子 ÷ 総重み) を算出。
  5. 最終的な収縮量: (個別の収縮比率 × 総溢れ量) をピクセルで算出。

この公式により、「初期サイズが大きく、かつ flex-shrink 値が高いアイテム」ほど、スペース不足時により多くの「犠牲(縮小ピクセル)」を払うことになります。

3. flex-basis(ベースサイズ)を深掘りする

flex-basis は、余白の分配(flex-grow)や圧縮(flex-shrink)が行われる前の、主軸方向におけるアイテムの初期サイズを指定します。

ピクセル (200px) やパーセント (50%) などの具体的な長さだけでなく、キーワード content も指定可能です。

3.1 長さ値の使用

flex-basis を具体的な長さ(例:200px)に設定すると、アイテムはそのサイズを起点として伸長・収縮を開始します。

.item-1 { flex-basis: 200px; }
.item-2 { flex-basis: 150px; }
.item-3 { flex-basis: 100px; }

3.2 flex-basis: content

content に設定すると(モダンブラウザのデフォルト挙動に近い)、アイテムの初期サイズは内部のコンテンツ(テキストの長さや画像の幅など)によって完全に決定されます。

3.3 flex-basis: 0 のテクニック

flex-basis0 に設定するのは、非常に重要なテクニックです。これは初期サイズをゼロとして扱うことを意味し、アイテムの最終的なサイズが完全に flex-grow の比率によって支配されるようになります。

.item-1 { flex-basis: 0; flex-grow: 1; }
.item-2 { flex-basis: 0; flex-grow: 2; }
.item-3 { flex-basis: 0; flex-grow: 1; }

初期サイズがすべて 0 なので、利用可能なすべてのスペースが「余白」となり、コンテンツ量に左右されない完璧な 1:2:1 の比率が実現します。

3.4 flex-basis vs. width / height

  • flex-direction: row の場合、flex-basiswidth のように動作します。
  • flex-direction: column の場合、flex-basisheight のように動作します。

決定的な違い: フレックスアイテムにおいては、flex-basis の優先順位が widthheight よりも高いということです。両方を記述した場合、ブラウザは flex-basis を採用し、width を無視します。

4. flex ショートハンドプロパティ

実際の開発現場では、flex-growflex-shrinkflex-basis を個別に書くことは稀です。代わりにショートハンドの flex プロパティを使用します。

構文:

flex: <flex-grow> <flex-shrink> <flex-basis>;

現場で多用するショートハンド値:

  • flex: 0 1 auto; (等価: flex: initial): デフォルト値。拡大せず、縮小はし、サイズはコンテンツに依存。
  • flex: 1; (等価: flex: 1 1 0;): 最も多用される設定。余白を埋めるために拡大し、不足すれば縮小。初期サイズは 0(コンテンツ量に依存しない)。
  • flex: auto; (等価: flex: 1 1 auto;): コンテンツ量に応じて自由自在に拡大・縮小。
  • flex: none; (等価: flex: 0 0 auto;): アイテムを完全に固定。拡大も縮小もしない。

5. 実装例:レスポンシブな料金表コンポーネント

学んだ知識を統合して、レスポンシブなプライシングテーブル(料金表)を作成してみましょう。

<div class="pricing-table">
  <div class="pricing-item">
    <h3>ベーシック</h3>
    <p class="price">¥9,900/月</p>
    <ul>
      <li>機能 1</li>
      <li>機能 2</li>
    </ul>
    <button>今すぐ開始</button>
  </div>
  
  <div class="pricing-item featured">
    <h3>プロ (おすすめ)</h3>
    <p class="price">¥19,900/月</p>
    <ul>
      <li>機能 1</li>
      <li>機能 2</li>
      <li>高度な機能 3</li>
    </ul>
    <button>今すぐ開始</button>
  </div>
  
  <div class="pricing-item">
    <h3>エンタープライズ</h3>
    <p class="price">¥49,900/月</p>
    <ul>
      <li>機能 1</li>
      <li>機能 2</li>
      <li>専任サポート</li>
    </ul>
    <button>お問い合わせ</button>
  </div>
</div>
.pricing-table {
  display: flex;
  justify-content: space-around;
  width: 100%;
}

.pricing-item {
  border: 1px solid #ccc;
  padding: 20px;
  text-align: center;
  flex: 1; /* 3つのカードでスペースを均等に分ける */
  margin: 10px;
}

.pricing-item.featured {
  border-color: gold;
  flex-grow: 1.2; /* おすすめプランを少し広くして強調 */
}

.pricing-item h3 {
  font-size: 1.5em;
  margin-bottom: 10px;
}

.pricing-item .price {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 20px;
}

.pricing-item ul {
  list-style: none;
  padding: 0;
  margin-bottom: 20px;
}

.pricing-item button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

/* レスポンシブ調整: モバイル対応 */
@media (max-width: 768px) {
  .pricing-table {
    flex-direction: column; /* 画面が狭くなったら垂直に並べる */
  }
  .pricing-item {
    margin: 10px 0;
    flex: initial; /* 垂直スタック時は初期サイズに戻す */
  }
  .pricing-item.featured {
    flex-grow: initial; /* 強調用の拡大設定もリセット */
  }
}

この例では、flex: 1 によって3つのプランを均等に配置し、featured クラスに flex-grow: 1.2 を与えることで視覚的なプライオリティを表現しています。さらにメディアクエリを使い、モバイル環境では flex-direction: column に切り替えることでスムーズなユーザビリティを実現しています。