フレックスアイテムのflex-basisとwidthプロパティの関係を理解する

フレックスアイテムの`flex-basis`と`width`プロパティ。ブラウザがアイテムの寸法を決定する際、この２つのプロパティ値はどのように作用するのでしょうか？ よく理解できていなかったのでサンプルを作って検証してみました。
![flex_all_auto_px.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/64f22322-4edd-ea78-fd8f-e85873608fb0.gif)
ソース:  https://codepen.io/kaz_hashimoto/pen/vYewQOK

今回作ったサンプルは、以下のプロパティ値を組み合わせたパターンごとにフレックスボックスの表示をまとめて確認できるので、自分用のチートシートにもなります。

| プロパティ | 値 |
|:--|:--|
| flex-grow  | 0, 1  |
| flex-shrink  |  0, 1 |
| flex-basis | px値, %値, auto   |
| width  |  px値, auto |


使用するフレックスボックスは３個のアイテムからなるコンテナーで、主軸は行方向です。

```html:html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three</div>
</div>
```

アイテムの`width`に指定するサイズは、px値、100%, autoの３種類です。px値については、３つの子要素の幅をそれぞれ50px, 100px, 150pxに設定してあります。下図は通常の（flexでない）レイアウトで表示した場合です。
![initial_400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/12b816f0-12ba-ed21-f43a-9123be7d6a97.png)
アイテムの背景に引かれたピンクの縦線の間隔は50pxです。また、コンテナーの下側 &mdash; ピンク色の境界線は、コンテナーの現在の幅を表す“インジケーター”です。

コンテナーの幅は画面右上のrange sliderを使って調節できます。初期値は400pxです。

```css:css
.box {
  width: 400px;
  display: flex;
}

.box > * {
  flex: 1 1 auto; /* 例 */
}
```

## flex-basisにpx値を指定した場合
### flex: 0 0 px値

最初の例として、アイテムの幅が**フレキシブルに変化しないケース**から見ていきましょう。
`flex-grow`と`flex-shrink`に0を指定して、`flex-basis`にpx値を指定した場合です。 図で２カラム目に"width: (px)"と表記したものは、３つのアイテムに幅50px, 100px, 150pxをそれぞれ設定したことを表します。
![flex0_0_px_width.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/81566ed0-405f-0a47-7e4b-d0366de56e6c.gif)
このサンプルのケースでは、アイテムに設定した`width`がpx値かautoかに関係なく
`flex-basis`が80pxの時は最終的なサイズは３つとも80pxに計算され、`flex-basis`が0の時は各アイテムのコンテントの最小幅に縮んでいるように見えます。

なぜこのような結果になるのか、[CSS Flexible Box Layout Module Level 1](https://www.w3.org/TR/css-flexbox-1/)に記載されているアイテムのサイズ計算のロジックを追ってみました。サイズ計算に使われるパラメーターを抜き出してそれらの関係をチャートにするとこんな感じです。
![chart_1920.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/7e6d5851-0a1e-1b12-edc0-bce2aee87f50.png)
なお、サイズ計算の説明を簡単にするため、フレックスコンテナーの主軸は行方向とし、交差軸のサイズ計算は考慮せず、アイテムのその他のプロパティ値については下表のとおりとします。

| プロパティ | 値 | 備考 |
|:--|:--|:--|
| box-sizing | border-box | |
| min-width | auto | 初期値 |
| max-width  | none  | 初期値 |
| flex-wrap | nowrap | 初期値 |

まず、アイテム"Three"について、flex-basis: 80px, width: 150pxの場合のサイズ計算を上記のチャートの変数x1〜x9にあてはめてみます。

| No. | 項目 | 値 | 備考 | section |
|:-|:------|:-:|:---|:---|
| x1 | content size suggestion | 62px | | 4.5 |
| x2 | specified size suggestion | 150px| | 4.5 |
| x3 | content-based minimum size | 62px | min(x1, x2) | 4.5 |
| x4 | flex base size  | 80px | | 9.2の3.A  |
| x5 | min main size | 62px | min-width = x3| 4.5 |
| x6 | max main size | &infin; | max-width: none | |
| x7 | hypothetical main size | 80px | clamp(x5, x4, x6)| 9.2の3 |
| x8 | target main size | 80px |  = x7 | 9.7の2 |
| x9 | main size | 80px | = x8 | 9.7の5 |

アイテムの最終的なサイズは確かに80pxと算出されました。
この表で変数x1の値は、サンプル図の3、４行目に表示されているアイテム"Tree"のdivの幅をDevToolsで取得したものです。表記を簡単にするため整数に丸めました。

ポイントは変数x5: [min main size](https://www.w3.org/TR/css-flexbox-1/#min-main-size)です。これは[min-width](https://www.w3.org/TR/css-sizing-3/#min-size-properties)のことで、今、初期値[auto](https://www.w3.org/TR/css-sizing-3/#valdef-width-auto)です。Flexible Box Layout Moduleにおける`auto`は[automatic minimum size](https://www.w3.org/TR/css-flexbox-1/#min-size-auto)で説明されている[content-based minimum size](https://www.w3.org/TR/css-flexbox-1/#content-based-minimum-size)、つまり変数x3の値になります。

次にアイテム"Three"について、flex-basis: 0px, width: 150pxの場合の計算表です。

| No. | 項目 | 値 | 備考 | section |
|:-|:------|:-:|:---|:---|
| x1 | content size suggestion | 62px | | 4.5 |
| x2 | specified size suggestion | 150px | | 4.5 |
| x3 | content-based minimum size | 62px | min(x1, x2) | 4.5 |
| x4 | flex base size | 0px  | | 9.2の3.A  |
| x5 | min main size | 62px | min-width = x3 | 4.5 |
| x6 | max main size | &infin; | max-width: none | |
| x7 | hypothetical main size | 62px| clamp(x5, x4, x6)| 9.2の3 |
| x8 | target main size | 62px |  = x7 | 9.7の2 |
| x9 | main size  | 62px| = x8 | 9.7の5 |

変数x4: flex base sizeは0ですが、x5: min main sizeの62pxで下側がclampされるため、最終的なサイズも62pxになります。

この２つのパターンを押さえれば、`flex-shrink`や`flex-grow`が1の場合に起きるサイズの伸縮を理解できます。前述のチャート右下の[9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)（変数x7 → x8）を通じてコンテナーのフリースペースがアイテムに配分されます。

### flex: 0 1 px値
`flex-shrink`が1の場合、コンテナーにフリースペースが残っている時は、各アイテムのサイズは「flex: 0 0 px値」の図と同じ状態になります。コンテナーの幅がアイテムのhypothetical main size（変数x7)の合計より短くなると`flex-shrink`が発動してアイテムのサイズが最小サイズまで縮まります。
![flex0_1_px_width.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/1e43a120-81e0-18ce-15a6-f3917a451b30.gif)

### flex: 1 0 px値
`flex-grow`が1の場合、コンテナーにフリースペースがある限り各アイテムに配分し尽くされるのでコンテナの幅一杯にアイテムで埋められます。コンテナーの幅がアイテムのhypothetical main size（変数x7)の合計より短い場合は、「flex: 0 0 px値」の図と同じ状態になります。
![flex1_0_px_width.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/e35d3988-a47d-8eaf-d814-3b26387efb5b.gif)

### flex: 1 1 px値
`flex-grow`, `flex-shrink`共に1の場合は、上記２つを併せた動きになります。つまり、shrinkが発動する時は`flex: 0 1 px値`のパターンに、growが発動する時は`flex: 1 0 px値`のパターンに帰着します。
![flex1_1_px_width.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/8508e660-5f26-f48a-5a16-cd8ec6d22f07.gif)

## flex-basisにautoを指定した場合
## flex: * * auto; width: px値
今度は`flex-basis`に`auto`を指定してみましょう。まず`width`がpx値の場合は下図に示した動きになります。
![flex_all_auto_px.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/64f22322-4edd-ea78-fd8f-e85873608fb0.gif)
これらの`flex-basis`の使用値(ピクセル数）はどこの寸法から取得しているのでしょうか？
１行目のアイテム"Three"に着目して、flex: 0 0 auto; width: 150pxの場合の計算表を埋めてみます。

| No. | 項目 | 値 | 備考 | section |
|:-|:------|:-:|:---|:---|
| x1 | content size suggestion | 62px | | 4.5 |
| x2 | specified size suggestion | 150px | | 4.5 |
| x3 | content-based minimum size | 62px | min(x1, x2) | 4.5 |
| x4 | flex base size  | 150px  | = width | 7.1  |
| x5 | min main size | 62px | min-width = x3 | 4.5 |
| x6 | max main size | &infin; | max-width: none | |
| x7 | hypothetical main size | 150px | clamp(x5, x4, x6)| 9.2の3 |
| x8 | target main size | 150px|  = x7 | 9.7の2 |
| x9 | main size | 150px | = x8 | 9.7の5 |

[7.1. The flex Shorthand](https://www.w3.org/TR/css-flexbox-1/#flex-property)によると、`<flex-basis>`が`auto`の場合はmain sizeプロパティ（主軸側、ここでは`width`）の値を`flex-basis`の使用値として取得することがわかります。

> auto
>    When specified on a flex item, the auto keyword retrieves the value of the main size property as the used flex-basis. If that value is itself auto, then the used value is content. 

したがって、変数x4: flex base sizeの値は150pxになり、これが変数x7のclamp()もパスして最終的なサイズになるのがわかります。

## flex: * * auto; width: auto
次に`width`も`auto`の場合です。

![flex_all_auto_auto.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/1c46bb6d-9326-bfb6-3300-63f54d53faf2.gif)

これも１行目のアイテム"Three"に着目して、flex: 0 0 auto; width: autoの場合の計算表を埋めてみます。

| No. | 項目 | 値 | 備考 | section |
|:-|:------|:-:|:---|:---|
| x1 | content size suggestion | 62px | | 4.5 |
| x2 | specified size suggestion | undefined | width: autoの場合はundefined | 4.5 |
| x3 | content-based minimum size | 62px | min(x1, x2) | 4.5 |
| x4 | flex base size  | 62px  | max-content inline size | 9.2の3.E |
| x5 | min main size | 62px | min-width = x3 | 4.5 |
| x6 | max main size | &infin; | max-width: none | |
| x7 | hypothetical main size | 62px | clamp(x5, x4, x6)| 9.2の3 |
| x8 | target main size | 62px|  = x7 | 9.7の2 |
| x9 | main size | 62px | = x8 | 9.7の5 |


前述の[7.1. The flex Shorthand](https://www.w3.org/TR/css-flexbox-1/#flex-property)、`<flex-basis>`が`auto`の場合の説明で、main sizeプロパティ(`width`)から取得した値も`auto`のときは使用値は`content`になります。

> If that value is itself auto, then the used value is content. 

で、`content`とはフレックスアイテムの中身に基づく自動のサイズを示します。これは[max-content inline size](https://www.w3.org/TR/css-sizing-3/#max-content)のことで、例えばテキストの場合、soft wrapなしの状態でテキストの長さにフィットする最も狭いインラインサイズです。

> content
>    Indicates an automatic size based on the flex item’s content. (It is typically equivalent to the max-content size, （以下略）

したがって、変数x4: flex base sizeの値は、テキスト"Three"がフィットする幅62pxとなります。これが変数x7のclamp()もパスして最終的なサイズになるのがわかります。

## min-contentとmax-contentの違い
これまでの例ではアイテムの内容が１ワードのみのテキストだったため、min-contentとmax-contentの違いがわかりません。そこで、３つ目のアイテムのテキストを３ワードの長いテキストに置き換えてみましょう。

```html:html
<div class="box">
  <div>One</div>
  <div>Two</div>
  <div>Three looooong words</div>
</div>
```

アイテム"Three.."に着目すると、１行目と３行目がテキストのsoft wrapなしの状態、つまりmax-content inline sizeの幅になっています。一方、２行目と４行目はflex-shrinkが発動し、テキストはsoft wrap可能な箇所はすべて折り返された状態、つまりmin-content inline sizeの幅になっています。

![flex_auto_auto_long_900.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/f39a95bc-a7bb-fe61-001e-c46df282e4fc.png)

アイテム"Three ..."について、flex: 0 0 auto; width: autoの場合の計算表を埋めてみます。

| No. | 項目 | 値 | 備考 | section |
|:-|:------|:-:|:---|:---|
| x1 | content size suggestion |88px | | 4.5 |
| x2 | specified size suggestion | undefined | width: autoの場合はundefined | 4.5 |
| x3 | content-based minimum size | 88px | min(x1, x2) | 4.5 |
| x4 | flex base size  | 182px  | max-content inline size | 9.2の3.E ,  |
| x5 | min main size | 88px | min-width = x3 | 4.5 |
| x6 | max main size | &infin; | max-width: none | |
| x7 | hypothetical main size | 182px | clamp(x5, x4, x6)| 9.2の3 |
| x8 | target main size | 182px|  = x7 | 9.7の2 |
| x9 | main size | 182px | = x8 | 9.7の5 |

ここで、

- 変数x1: min-content inline size : soft wrapした場合のサイズ(図の２, 4行目）
- 変数x4: max-content inline size : soft wrapをしない場合のサイズ（図の1, 3行目）

そして変数x4の182pxが以降のclamp()もパスして最終的なサイズになります。

## flex-basisにパーセント値を指定した場合
最後に`flex-basis`にパーセント値を指定した場合です。これはアイテムの包含ブロックのサイズ（この場合は幅）に対するパーセント値なので、「**flex-basisにpx値を指定した場合**」における「px値」を「包含ブロックの幅」に置き換えたものに帰着します。つまり、変数x4: flex base sizeはdiv.boxの幅に、変数x7: hypothetical main sizeはx4をアイテムの最小幅と最大幅でclamp()したサイズになります。

以下に示した２つの例は`flex-basis`に100%を指定した場合です。ここではアイテムのhypothetical main sizeの合計がdiv.boxの幅より大きくなるため、アイテムにflex係数が指定されている場合は`flex-shrink`が発動して収縮します。
（詳細: [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)）

## flex: * * 100%; width px値
![flex_all_pct_px.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/e06d0115-85bf-63db-4cac-dd7995d44b20.gif)

## flex: * * 100%; width: auto

![flex_all_pct_auto.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/f8ea55d4-aa9e-8913-b098-ff3175f10835.gif)