フレックスアイテムのflex-basis
とwidth
プロパティ。ブラウザがアイテムの寸法を決定する際、この2つのプロパティ値はどのように作用するのでしょうか? よく理解できていなかったのでサンプルを作って検証してみました。
ソース: https://codepen.io/kaz_hashimoto/pen/vYewQOK
今回作ったサンプルは、以下のプロパティ値を組み合わせたパターンごとにフレックスボックスの表示をまとめて確認できるので、自分用のチートシートにもなります。
プロパティ | 値 |
---|---|
flex-grow | 0, 1 |
flex-shrink | 0, 1 |
flex-basis | px値, %値, auto |
width | px値, auto |
使用するフレックスボックスは3個のアイテムからなるコンテナーで、主軸は行方向です。
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
アイテムのwidth
に指定するサイズは、px値、100%, autoの3種類です。px値については、3つの子要素の幅をそれぞれ50px, 100px, 150pxに設定してあります。下図は通常の(flexでない)レイアウトで表示した場合です。
アイテムの背景に引かれたピンクの縦線の間隔は50pxです。また、コンテナーの下側 — ピンク色の境界線は、コンテナーの現在の幅を表す“インジケーター”です。
コンテナーの幅は画面右上のrange sliderを使って調節できます。初期値は400pxです。
.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値を指定した場合です。 図で2カラム目に"width: (px)"と表記したものは、3つのアイテムに幅50px, 100px, 150pxをそれぞれ設定したことを表します。
このサンプルのケースでは、アイテムに設定したwidth
がpx値かautoかに関係なく
flex-basis
が80pxの時は最終的なサイズは3つとも80pxに計算され、flex-basis
が0の時は各アイテムのコンテントの最小幅に縮んでいるように見えます。
なぜこのような結果になるのか、CSS Flexible Box Layout Module Level 1に記載されているアイテムのサイズ計算のロジックを追ってみました。サイズ計算に使われるパラメーターを抜き出してそれらの関係をチャートにするとこんな感じです。
なお、サイズ計算の説明を簡単にするため、フレックスコンテナーの主軸は行方向とし、交差軸のサイズ計算は考慮せず、アイテムのその他のプロパティ値については下表のとおりとします。
プロパティ | 値 | 備考 |
---|---|---|
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 | ∞ | 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、4行目に表示されているアイテム"Tree"のdivの幅をDevToolsで取得したものです。表記を簡単にするため整数に丸めました。
ポイントは変数x5: min main sizeです。これはmin-widthのことで、今、初期値autoです。Flexible Box Layout Moduleにおけるauto
はautomatic minimum sizeで説明されている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 | ∞ | 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になります。
この2つのパターンを押さえれば、flex-shrink
やflex-grow
が1の場合に起きるサイズの伸縮を理解できます。前述のチャート右下の9.7. Resolving Flexible Lengths(変数x7 → x8)を通じてコンテナーのフリースペースがアイテムに配分されます。
flex: 0 1 px値
flex-shrink
が1の場合、コンテナーにフリースペースが残っている時は、各アイテムのサイズは「flex: 0 0 px値」の図と同じ状態になります。コンテナーの幅がアイテムのhypothetical main size(変数x7)の合計より短くなるとflex-shrink
が発動してアイテムのサイズが最小サイズまで縮まります。
flex: 1 0 px値
flex-grow
が1の場合、コンテナーにフリースペースがある限り各アイテムに配分し尽くされるのでコンテナの幅一杯にアイテムで埋められます。コンテナーの幅がアイテムのhypothetical main size(変数x7)の合計より短い場合は、「flex: 0 0 px値」の図と同じ状態になります。
flex: 1 1 px値
flex-grow
, flex-shrink
共に1の場合は、上記2つを併せた動きになります。つまり、shrinkが発動する時はflex: 0 1 px値
のパターンに、growが発動する時はflex: 1 0 px値
のパターンに帰着します。
flex-basisにautoを指定した場合
flex: * * auto; width: px値
今度はflex-basis
にauto
を指定してみましょう。まずwidth
がpx値の場合は下図に示した動きになります。
これらのflex-basis
の使用値(ピクセル数)はどこの寸法から取得しているのでしょうか?
1行目のアイテム"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 | ∞ | 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によると、<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
の場合です。
これも1行目のアイテム"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 | ∞ | 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、<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のことで、例えばテキストの場合、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の違い
これまでの例ではアイテムの内容が1ワードのみのテキストだったため、min-contentとmax-contentの違いがわかりません。そこで、3つ目のアイテムのテキストを3ワードの長いテキストに置き換えてみましょう。
<div class="box">
<div>One</div>
<div>Two</div>
<div>Three looooong words</div>
</div>
アイテム"Three.."に着目すると、1行目と3行目がテキストのsoft wrapなしの状態、つまりmax-content inline sizeの幅になっています。一方、2行目と4行目はflex-shrinkが発動し、テキストはsoft wrap可能な箇所はすべて折り返された状態、つまりmin-content inline sizeの幅になっています。
アイテム"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 | ∞ | 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した場合のサイズ(図の2, 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()したサイズになります。
以下に示した2つの例はflex-basis
に100%を指定した場合です。ここではアイテムのhypothetical main sizeの合計がdiv.boxの幅より大きくなるため、アイテムにflex係数が指定されている場合はflex-shrink
が発動して収縮します。
(詳細: 9.7. Resolving Flexible Lengths)