1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Frontend CSS – パート8】Flexboxの内部アルゴリズム – ブラウザはフレックスアイテムをどう計算するのか?

1
Posted at

ChatGPT Image Jun 12, 2026, 03_49_53 PM.png

注意
この記事はAIの助けを借りていますが、内容は大規模Webアプリケーションでの実務経験に基づいています。


1. 問題:「Flexboxよ、なぜこんなに悩ませるの?」

こんな経験、ありませんか?

  • シチュエーション1: 3つのカードを横に並べて、きれいに等間隔にしたい。display: flex; justify-content: space-between; を書いたのに、左右の子要素は端っこに張り付き、真ん中は…あれ? ちゃんと中央にならない。margin: auto とか試してみたら、ますますカオスに。

  • シチュエーション2: ナビゲーションバーを作りたい。ロゴは左、メニューは中央、アバターは右。メニューに flex: 1 を指定すれば、余ったスペースを全部使ってくれるはず…と思いきや、画面が狭くなるとメニューがロゴと重なったり、アバターがはみ出したり。「なんで思った通りに縮んでくれないの?」ってなりますよね。

  • シチュエーション3: アイテムのリストがあって、それぞれに「とっても長い商品名(どうしても改行できない)」が入っている。全部のアイテムに flex: 1 を指定して、均等にしたい。結果は? アイテムは確かに均等に広がったけど、中のテキストがはみ出してレイアウトが崩れる。overflow: hidden を追加しても全然ダメ。

  • シチュエーション4: サイドバーを幅250pxで固定して、残りの部分を伸縮させたい。サイドバーに flex: 0 0 250px、コンテンツに flex: 1。これで完璧! …と思ったら、サイドバーに padding を加えた途端、幅が250pxより大きくなって、コンテンツが画面外にはみ出した。いったい何が起きてるの?

シチュエーション4の簡単な説明:
flex: 0 0 250px を指定すると、この 250pxflex-basis になります。デフォルトでは、要素は box-sizing: content-box です。そこに padding を追加すると、実際の幅は flex-basis + padding-left + padding-right になります。
解決策: * { box-sizing: border-box; } で全要素をリセットすれば、padding と border が指定したサイズの内側に収まります。これはモダンなプロジェクトでは標準的なやり方です。

正直なところ: たいていの人はFlexboxを「試行錯誤」で使っています。flex: 1 は「残り全部を食べる」、justify-content: center は「中央に揃える」と思っている。そしてレイアウトが崩れたら、widthmax-widthflex-shrink: 0 をやみくもに追加し始める。それはFlexboxを使いこなしているのではなく、運任せなんです。

今日は、Flexboxの内部アルゴリズムを徹底的に解剖します。なぜそんな動きになるのか、そしてどんな状況でも正しく動作を予測する方法を理解できるようになりましょう。


2. 本質:Flexboxは「直感」ではなく「アルゴリズム」である

blockinline が自然なフローに依存しているのとは違い、Flexboxは能動的なレイアウトモデルです。要素を並べるだけでなく、一連のルールに基づいてスペースを計算し、分配します。

Flexboxを、コンテナとアイテムの間の交渉だと思ってください。

何よりも重要なこと: Flexboxは無闇にスペースを均等分割しているわけではありません。各アイテムの要求量 (flex-basis) をベースにして、その過不足flex-growflex-shrink の比率に従って分配しているんです。

これを理解しないと、いつまで経っても flex の数値に振り回されることになりますよ。


3. FlexコンテナとFlexアイテムの構造を解剖する

アルゴリズムの詳細に入る前に、まずは核となる概念を整理しましょう。

3.1. 2つの軸: main axis と cross axis

  • Main axis(主軸): アイテムが並ぶ主要な方向(デフォルトは水平)。
  • Cross axis(交差軸): 主軸と垂直な軸。

3.2. Flexアイテムの重要なプロパティ

プロパティ 役割 デフォルト値
flex-basis 主軸方向におけるアイテムの希望サイズ auto (内容のサイズに依存)
flex-grow 余ったスペースをどれだけ受け取るかの比率 0 (受け取らない)
flex-shrink スペースが足りない時にどれだけ縮むかの比率 1 (縮むことができる)
flex (一括指定) flex-grow, flex-shrink, flex-basis をまとめて指定 0 1 auto

3.3. flex: 1 の真実

あなたは flex: 1 を「残りのスペースを均等に分ける」と思っていませんか? それは違います。 flex: 1 は実際には次のショートハンドです。

flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;

注意深い読者のためのメモ:
最近のブラウザはどれも flex: 11 1 0% として解決します。歴史的には仕様に微妙な違いがありましたが(00% で、00px と解釈されるケースもあるなど)、実際のレイアウトではほぼ同じように動作します。flex: 1 を標準的な値として安心して使って大丈夫です。

ここで重要なのは flex-basis: 0% です。これは**「私の希望する初期サイズは0です」という意味です。そして flex-grow: 1コンテナのスペース全体**(他のアイテムのコンテンツを差し引いた残りではなく)を均等に分配します。

これは flex: auto (つまり 1 1 auto) とは全く異なります。flex: auto では flex-basis: auto により、分配前に各アイテムの内容サイズが尊重されます。


4. Flexboxアルゴリズム:スペース分配の4ステップ

ブラウザは、レイアウトを再計算する必要があるたび(リサイズ時、アイテムの追加・削除、内容の変更など)に、以下のステップを実行します。

ステップ1: コンテナの main size と cross size を決める

  • Main size: コンテナの幅(flex-direction: row の場合)または高さ(column の場合)。
  • ブラウザはコンテナの width/heightmin-width/min-heightmax-width/max-height をチェックします。

ステップ2: flex-basis を設定する – 各アイテムの「希望」

ブラウザは各アイテムに「主軸方向でどのくらいのスペースが欲しい?」と尋ねます。

  • flex-basis: auto の場合: width(または height)があればそれを使い、なければ内容のサイズに基づきます。
  • flex-basis に具体値(px, % など)が指定されている場合: その値を使います。
  • flex-basis: 0% の場合: 希望は0からスタート。

重要: flex-basis最終的なサイズではありません。あくまで交渉の開始点です。

ステップ3: スペースが余った場合の処理 – flex-grow

どんな時に発生する?
全アイテムの flex-basis の合計が、コンテナの main size よりも小さい場合。

余剰スペースの分配公式:

余剰スペース = コンテナの main size - 全アイテムの flex-basis 合計

各アイテムに追加される分 = 余剰スペース × (そのアイテムの flex-grow) / (全アイテムの flex-grow 合計)

落とし穴: flex-grow最終的なサイズの比率を保証するものではありません。あくまで余剰スペースの分配比率です。

ステップ4: スペースが足りない場合の処理 – flex-shrink

どんな時に発生する?
全アイテムの flex-basis の合計が、コンテナの main size よりも大きい場合。

不足スペースの分配(縮小)公式:

flex-grow とは異なり、flex-shrink は各アイテムの現在のサイズを考慮します。大きいアイテムほど、より多く縮むことになります(比率に応じて)。

不足スペース = 全 flex-basis 合計 - コンテナの main size

各アイテムの『縮みの重み』 = (flex-basis) × (flex-shrink)

各アイテムの縮小量 = 不足スペース × (そのアイテムの縮みの重み) / (全アイテムの縮みの重みの合計)

注意深い読者のためのメモ:
上記の公式は、CSS Flexbox仕様にある実際のアルゴリズムを単純化したものです。実際のブラウザは min-width/max-width の制約に基づいてアイテムを freeze/unfreeze しながら、複数回の反復処理を行います。しかし、この基本公式を理解しているだけでも、実務上のほとんどのケースでレイアウトを予測・デバッグするのに十分です。

flex-shrink に関する重要な注意:
flex-shrinkflex-basis: auto の場合でもちゃんと動作します。その場合、縮小計算に使われるサイズは flex base size(通常は width または内容の intrinsic size に基づく)となります。さらに、デフォルトの min-width: auto が期待通りに縮むのを妨げることがあります – この点は常にチェックしてください。


5. よくある落とし穴と「あまり知られていない魔法」

5.1. 最もよくあるFlexboxのバグ:min-width: 0 の忘れ物

これが、flexアイテムが思ったように縮まない一番の原因です。

デフォルトでは、すべてのflexアイテムには min-width: auto(row方向の場合)または min-height: auto(column方向の場合)が設定されています。これはつまり、アイテムは内容のサイズより小さく縮むことができないということです。

/* ブラウザはデフォルトでこれを追加しているようなもの */
.item {
  min-width: auto; /* または min-height: auto */
}

典型的な例:

<div class="container">
  <div class="item">すごくすごく長いテキストでどうしても改行したくない</div>
  <div class="item">短い</div>
</div>
.container {
  display: flex;
  width: 300px;
}
.item {
  flex: 1; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0% */
}

結果: 最初のアイテムは縮まずにはみ出しますmin-width: auto が内容より小さくなるのを防いでいるからです。

解決策: min-width: 0 を設定するか、overflowvisible 以外にします(overflowvisible 以外の場合、flexアイテムは automatic minimum size が変わるため、内容より小さく縮むことが可能になります)。

.item {
  flex: 1;
  min-width: 0; /* 内容より小さく縮むことを許可 */
  /* または */
  overflow: auto;
}

5.2. Flexboxにおける margin: auto の「魔法」

margin: auto はFlexboxの中で、ブロックレイアウトとは異なる動きをします。なんと、主軸方向の余ったスペースをすべて吸収してしまうのです。

.navbar {
  display: flex;
}
.logo {
  margin-right: auto; /* ロゴの右側にある余ったスペースを全部吸収 */
}

結果: ロゴは左端にピタリとくっつき、残りのスペース(メニュー+アバター)はすべて右側に押し出されます。

間違えないで: margin-right: auto は、後ろの要素を右に押しやるだけで、メニューを中央に自動配置したりはしません。メニューを中央にしたい場合は、メニュー自身に margin: 0 auto を指定するか、アバターに margin-left: auto を組み合わせるなどの工夫が必要です。

これは、ロゴを左に、アクションアイテムを右に配置したいナビゲーションバーなどでとても便利なテクニックです。

5.3. Flexアイテム内でのパーセント指定の padding/margin の真実

flexアイテムに padding: 10% を指定した場合、このパーセント値は包含ブロックのインラインサイズ(通常はflexコンテナの幅であり、アイテム自身の幅ではない)に基づいて計算されます。これにより、包含ブロックのサイズが伸縮の過程で変わるため、予測しづらい結果を生むことがあります。

例:
コンテナの幅が500pxのとき、アイテムに padding: 10% を指定すると、padding は 50px(500pxの10%)になります。アイテム自身の現在の幅は関係ありません。

5.4. flex-basis: 0%flex-basis: auto の違い

動作
flex-basis: 0% 全アイテムが0からスタート。スペースは flex-grow の比率で均等に分配される
flex-basis: auto アイテムは内容サイズからスタート。余ったスペースだけが分配される

例を見てみましょう。

/* ケース1: flex-basis: 0% */
.container > * {
  flex: 1; /* 1 1 0% */
}
/* 結果: すべてのアイテムのサイズが等しくなる */

/* ケース2: flex-basis: auto */
.container > * {
  flex: auto; /* 1 1 auto */
}
/* 結果: 内容の長いアイテムほど幅が広くなる */

5.5. ボーナス: flex-basis vs width – どちらが優先される?

これは非常によくある質問です。flexアイテムに widthflex-basis の両方が指定されている場合、どちらが使われるのでしょうか?

シンプルなルール:
flex-basisauto ではない場合、flex-basiswidth(または主軸方向に応じた height)よりも優先されます。

.item {
  width: 300px;
  flex-basis: 100px; /* flex-basis が勝つ → 希望サイズは 100px */
}

唯一の例外:
flex-basis: auto(または未指定でデフォルトの auto)の場合、ブラウザは width(または height)を flex base size として使います。

.item {
  width: 300px;
  flex-basis: auto; /* または flex-basis を書かない → width の 300px を使う */
}

クイックまとめ:

flex-basis 動作
具体値(px, % など) その値を使う。width は無視される
auto width(または height)を使う。なければ内容に依存

これは、伸縮させる前の初期サイズを正確に制御したい場合に非常に重要です。


6. React TypeScriptで実践:スマートに伸縮するダッシュボードカード

flex-growflex-shrinkflex-basis の動作を(flex-wrap の誤解なしに)明確に示すために、ダッシュボードのカード行を実際に作ってみましょう。

要件:

  • コンテナの幅は100%(レスポンシブ対応)
  • カードA(最も重要): 他のカードの2倍のスペースを取る → flex: 2(つまり 2 1 0%
  • カードBとC: 普通 → flex: 1(つまり 1 1 0%
  • 画面が小さくなっても(600px以下)、カードは縮みつつ比率を維持flex-shrink: 1 のおかげ)
  • ただし画面が極端に小さい場合(480px未満)は、縦並びに切り替える(Flexboxの横並びをやめる)。
DashboardCards.tsx
import React from 'react';
import './DashboardCards.css';

interface CardData {
  title: string;
  value: string | number;
  trend?: 'up' | 'down';
  flexValue: number; // flex-grow
}

const cards: CardData[] = [
  { title: '売上高', value: '125億', trend: 'up', flexValue: 2 },
  { title: 'アクセス数', value: '45.2K', trend: 'up', flexValue: 1 },
  { title: '直帰率', value: '32%', trend: 'down', flexValue: 1 },
];

export const DashboardCards: React.FC = () => {
  return (
    <div className="dashboard-cards">
      {cards.map((card) => (
        <div 
          key={card.title} 
          className="card"
          style={{ flex: `${card.flexValue} 1 0%` }} // flex-grow, flex-shrink=1, flex-basis=0%
        >
          <h3 className="card-title">{card.title}</h3>
          <p className="card-value">{card.value}</p>
          {card.trend && (
            <span className={`card-trend ${card.trend}`}>
              {card.trend === 'up' ? '' : ''}
            </span>
          )}
        </div>
      ))}
    </div>
  );
};
DashboardCards.css
.dashboard-cards {
  display: flex;           /* 誤解を避けるため、ここでは flex-wrap: wrap は使わない */
  gap: 1rem;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 1rem;
}

.card {
  background: #ffffff;
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  transition: all 0.2s;
  
  /* 内容がレイアウトを壊さないようにするために必須 */
  min-width: 0;        /* 必要なら内容より小さく縮むことを許可 */
  overflow: hidden;
}

/* 480px未満になったら縦並びに切り替え */
@media (max-width: 480px) {
  .dashboard-cards {
    flex-direction: column;  /* row → column に変更 */
  }
  .card {
    flex: 1 1 auto !important; /* flex をリセット。縦方向では各カードが自由に */
  }
}

.card-title {
  font-size: 0.875rem;
  color: #666;
  margin-bottom: 0.5rem;
}

.card-value {
  font-size: 1.8rem;
  font-weight: bold;
  margin: 0;
  /* word-break ではなく overflow-wrap: break-word を使う(仕様により正確) */
  overflow-wrap: break-word;
}

.card-trend {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: 0.875rem;
}

.card-trend.up {
  color: #10b981;
}

.card-trend.down {
  color: #ef4444;
}

この例(flex-wrap なし)でのアルゴリズム解説:

  • コンテナ .dashboard-cards の幅は、例えば 1200px とします。
  • 各カードは flex-basis: 0% です(flex: flexValue 1 0% の部分)。
  • 全アイテムの flex-basis 合計 = 0。
  • flex-basis: 0% なので、コンテナの幅 1200px 全体が『余剰スペース』になります。
    (もし flex-basis が 0 以外や auto だったら、ブラウザはまず初期サイズを差し引いてから余剰を計算します。)
  • flex-grow 合計 = 2 + 1 + 1 = 4。
  • カードA の取得幅: 1200 × 2/4 = 600px
  • カードB の取得幅: 1200 × 1/4 = 300px
  • カードC の取得幅: 1200 × 1/4 = 300px

画面が小さくなって、例えばコンテナ幅が 600px になった場合:

  • flex-basis 合計は変わらず 0。
  • 600px 全体が余剰スペース。
  • カードA = 600 × 2/4 = 300px
  • カードB = 600 × 1/4 = 150px
  • カードC = 150px

比率 2:1:1 は常に保たれます。 これが flex-basis: 0%flex-grow の比率による効果です。

画面が非常に小さくなった場合(480px未満):
あえて flex-direction: column に切り替えて見やすくし、flex もリセットして各カードが縦方向で自然に積まれるようにしています。


7. チェックリスト:Flexboxを呪う前に確認すること

Flexboxが思った通りに動かないときは、以下のポイントを一つずつチェックしてみてください。

  • 主軸(main axis)の方向は正しく理解できていますか?
    flex-direction: row → 主軸は水平。column → 主軸は垂直。

  • 各アイテムの flex-basis はいくつになっていますか?
    auto(内容/width に依存)、0%(0からスタート)、それとも具体的な数値?

  • 全アイテムの flex-basis 合計は、コンテナより大きいですか、小さいですか?
    大きい → flex-shrink が介入。小さい → flex-grow が介入。

  • min-width: automin-height: auto が縮小を妨げていませんか?
    試しに min-width: 0overflowvisible 以外に設定して、内容より小さく縮めることを許可してみてください。

  • margin: auto で余白調整を解決できませんか?
    flex-grow を使う前に、margin-left: automargin-right: auto を試してみる価値があります。

  • パーセント指定の padding/margin の影響を確認しましたか?
    パーセント値は包含ブロック(通常はコンテナ)を基準に計算されます。px や rem を使うか、ネストしたラッパーを検討しましょう。

  • flex: 1flex: auto を混同していませんか?
    flex: 11 1 0%(0ベースで均等に)
    flex: auto1 1 auto(内容を尊重)

  • flex-basiswidth で上書きされていませんか?
    flex-basisauto 以外)は常に width より優先されることを覚えておきましょう。width を使いたい場合は flex-basis: auto を指定してください。

  • box-sizing: border-box のリセットは済んでいますか?
    もしリセットしていないと、flex-basispadding が加算されて予想より大きくなることがあります。これはよく見落とされる原因のひとつです。


8. まとめと参考資料

Flexboxは魔法でもなければ、「当たって砕けろ」で試すようなものでもありません。それは明確なアルゴリズムを持ったレイアウトシステムです。

  1. コンテナのスペースを決める
  2. 各アイテムに「どれだけ欲しい?」と尋ねる(flex-basis
  3. 余っていれば flex-grow で分配
  4. 足りなければ flex-shrink で縮小(重み付けあり)

これを理解すれば、あなたは次のレベルに到達できます。

  • コードを書く前にレイアウトを予測できる
  • デバッグが早くなる(手探りじゃなくなる)
  • メディアクエリを何百も書かなくてもレスポンシブ対応が最適化できる
  • margin: auto などの賢いテクニックを活用して、CSSの記述量を減らせる

覚えておいてほしい呪文: 「flex-basis は希望、flex-grow と flex-shrink は交渉、min-width は最後の壁」


さらに深く学びたい方への参考資料:


次回予告:

👉 【Frontend CSS – パート9】ブラウザから見たCSS Grid:2次元レイアウトは実際どう動くのか?

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?