flex-grow, flex-shrink, flex-basis について
flex-grow CSS プロパティは、flex アイテムの flex grow factor を指定します。これは、アイテムが flex コンテナ内のスペースをどれだけ占有するかを指定します。
MDN: flex-grow
MDN の説明がシンプルすぎてよく分からなかったので、もう少し詳しく調べた内容をまとめておきます。
説明中に出てくるスクショの動作サンプルは下の記事にあります。
この記事は上の動作サンプル記事が長くなっため分離した補足記事みたいな感じです。
flexbox
の基本
flexbox の基本的な動作は以下のページが個人的には分かりやすかったのでオススメです。
- CSS3 Flexbox の各プロパティの使い方をヴィジュアルで詳しく解説
flexbox の仕様全般に関する説明が分かりやすい - Flexboxを使うなら知っておきたい「flexアイテム」の幅の計算方法
flex-grow に関する説明が分かりやすい
flex-grow
ある flex アイテムが、 flex コンテナ内の他の flex アイテムと比較して、どのくらい大きくなろうとするかを整数値で指定する。
W3C の flex grow
の説明にはこうある。
This
component sets flex-grow longhand and specifies the flex grow factor, which determines how much the flex item will grow relative to the rest of the flex items in the flex container when positive free space is distributed. When omitted, it is set to 1.
W3C: flex-grow
意味としては、
この数値は、ポジティブなフリースペースが分配された時に、 flex アイテムが flex コンテナ内の他の flex アイテムと比較してどれくらい増大されるかを決定します。
ということだと思う。
flex-grow
はあくまでも余ったスペースを各アイテムで分け合う。
分配されるのは領域全体ではなく、余ったスペース(以下ポジティヴなフリースペース)。
ある領域を1:2:3
の比率で分割したいとして、単純に flex-grow
をそれぞれ1
,2
,3
と指定しても上手くいかない。
上は width
を 60px
, 120px
, 180px
に指定した display: block
の要素。
下は flex-grow
を 1
, 2
, 3
に指定した display: flex
の要素。
上と下を見比べると、下の比率が1:2:3
になっていないことが分かる。
下の図について細く見ていくと、余ったスペースが各アイテムに対してどのように分配されているのかが分かる。
flex コンテナの width
は360px
。
flex アイテムの width
は auto
、 flex アイテム内のコンテンツは40px
の span
要素なので、 flex アイテムの幅は40px
となる。
flex-grow
が分け合うポジティヴなフリースペースとは、 flex コンテナから flex アイテムの幅を引いた値。360px - (40px + 40px + 40px) = 240px
のことを指す。
この240px
を flex-grow
の値1:2:3
の比率で分配し、 width
に足した合わせた値が最終的な幅となる。
(40 + 1/6 * 240) + (40 + 2/6 * 240) + (40 + 3/6 * 240)
80 + 120 + 160
この例では比率が2:3:4
になる。
flex-shrink
ある flex アイテムが、 flex コンテナ内の他の flex アイテムと比較して、どのくらい小さくなろうとするかを整数値で指定する。
W3C の flex-shrink
の説明にはこうある。
This
component sets flex-shrink longhand and specifies the flex shrink factor, which determines how much the flex item will shrink relative to the rest of the flex items in the flex container when negative free space is distributed.
W3C: flex-shrink
意味としては、
この数値は、ネガティブなフリースペースが分配された時に、 flex アイテムが flex コンテナ内の他の flex アイテムと比較してどれくらい縮小されるかを決定します。
ということだと思う。
ネガティヴなフリースペースというが分かりにくい。
flex コンテナの幅と flex アイテムの幅の差がポジティヴかネガティヴかというふうに考えると分かりやすいかもしれない。
flex-grow
の時と逆の状況を考える。
flex アイテムの width
を 160px
にして、 flex コンテナからはみ出すようにしてみる。
- flex アイテムを全て足した幅が flex コンテナより小さい
360px - (40px + 40px + 40px) = 240px
= ポジティブなフリースペース。 - flex アイテムを全て足した幅が flex コンテナより大きい
360px - (160px + 160px + 160px) = -120px
= ネガティヴなフリースペース
flex-shrink
はこのネガティヴなフリースペースをどう分配するか、ということを指定する。
下のスクショは、
上が display: block
+ float: left
、 width: 120px
の要素。
下が flex-shrink
を 1
,2
,3
に指定した display: flex
の要素となっている。
ネガティヴなフリースペース -120px
を、 flex-shrink
の値1:2:3
の比率で分配すると、
(160 + 1/6 * -120) + (160 + 2/6 * -120) + (160 + 3/6 * -120)
140 + 120 + 100
flex アイテムはそれぞれ140px
、120px
、100px
となり、比率は7:6:5
となる。
flex-basis
flex-basis
についてはまだ仕様が不安定な印象です。
とりあえず MDN の説明を引用。
flex-basis CSS プロパティは、flex アイテムの初期 main size である flex basis を指定します。box-sizing を使用して別に指定されていない限り、このプロパティが content-box の寸法を定義します。
MDN: flex-basis
要はベースとなる width
か height
を指定するということだと思う。
box-sizing
の影響を受けるので、 border
、 padding
を含めた値を指定したい場合は box-sizing
を border-box
にする。
(ただし、IEでは box-sizing
の指定に関するバグ有り。 Flexbugs: 7. flex-basis doesn't account for box-sizing:border-box)
指定出来る値は width
、 height
に設定出来る値であれば何でも良い。
あるいは、 content
を指定する。
content
結論から書くと、動作がブラウザによってバラバラなので、現段階では使わないほうが良さそうです(2016年9月4日)。
コンテンツサイズに応じてよしなにやってくれる的な設定なんじゃないかーぐらいのことしか分かりませんでした。
W3C の説明も
Indicates automatic sizing, based on the flex item’s content.
と一行だけです。
実際の動作サンプルについては別記事に追記があります。
メモを揉め: Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis
ブラウザ間の差異の原因になっている(2016年9月4日現在)
flex-basis
は Chrome 、 Firefox 、 Edge グループと Safari 、 IE11 グループとで動作が異なる。
2017年8月17日現在は IE11 のみ動作が異なる。
Chrome 、 Firefox 、 Edge はコンテンツのサイズを維持。
Safari 、 IE11 はコンテンツサイズを無視する。
Chrome 、 Firefox 、 Safari 、 Edge はコンテンツのサイズを維持。
IE11 はコンテンツサイズを無視する。
こちらも詳しくは別記事に記述があります。
メモを揉め: Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis
その他の考慮すべきこと
ここまでの flex-grow
、 flex-shrink
の動作は、余った、あるいは足りないスペースを flex-grow
、 flex-shrink
に基いて分配するという、ある意味シンプルなルールです。
しかし、場合によっては様々な要因によって結果が予想しにくくなることがあります。
再計算が必要になる場合
フリースペースの分配が行われた後、何らかの原因によって再びネガティブスペースを作ってしまった場合、再計算が行われます。
0以下になる場合
flex
コンテナの幅が 360px
、
flex
アイテムの flex-shrink
が 1
、 2
、 3
、
width
が 400px
、
min-wditn
が 0
に設定されていた場合、下のスクショの様な結果になる。
順を追って見ていくと、
flex
コンテナからflex
アイテム3つの合計を引く
360 - (400 + 400 + 400) = -840
- ネガティブなフリースペース
-840px
をflex-shrink
に基いて分配する
(400 - 1/6 * 840) + (400 - 2/6 * 840) + (400 - 3/6 * 840)
- 3つ目の
flex
アイテムが0
を下回ってしまう
(260) + (120) + (-20)
- 再び生まれたネガティブなフリースペース
-20px
の再分配が行われる
(260 - 1/3 * 20) + (120 - 2/3 * 20) + (0)
- 最終的なサイズが決定する
253.3333... + 106.6666... + 0
コンテンツサイズを下回る場合
Chrome 、 Firefox 、 Edge の flex-basis
の挙動により起こる現象。
flex
コンテナの幅が 360px
、
flex
アイテムの flex-grow
が 1
、 5
、 6
、
flex-basis
が 0
に設定されていた場合、下のスクショの様な結果になる。
順を追って見ていくと、
flex
コンテナからflex
アイテム3つの合計を引く
360 - (0 + 0 + 0) = 360
- ポジティブなフリースペース
360px
をflex-grow
に基いて分配する
(0 + 1/12 * 360) + (0 + 5/12 * 360) + (0 + 6/12 * 360)
- 1つ目の
flex
アイテムがコンテンツサイズの40px
を下回ってしまう
(30) + (150) + (180)
- 1つ目の
40px
を除いた320px
が再びポジティブなフリースペースとして再分配される(40) + (0 + 5/11 * 320) + (180 + 6/11 * 320)
- 最終的なサイズが決定する
40 + 145.4545... + 174.5454...
margin, paddingを下回る場合
margin
、 padding
は flex-grow
、 flex-shrink
によって増大も縮小もしないため、フリースペースの分配結果がこれらを下回る場合は再分配が行われる。
flex
コンテナの幅が 360px
、
flex
アイテムの flex-shrink
が 1
、 2
、 3
、
width
が 160px
、
左右の padding
が 40px
、
min-width
が 0
に設定されていた場合、下のスクショの様な結果になる。
flex
コンテナからflex
アイテム3つの合計を引く
360 - (240 + 240 + 240) = -360
- ポジティブなフリースペース
360px
をflex-grow
に基いて分配する
(240 - 1/6 * 360) + (240 - 2/6 * 360) + (240 - 3/6 * 360)
- 3つ目の
flex
アイテムが左右のpadding
の合計80px
を下回ってしまう
(180) + (120) + (60)
- 再び生まれたネガティブなフリースペース
-20px
の再分配が行われる
(180 - 1/3 * 20) + (120 - 2/3 * 20) + (80)
- 最終的なサイズが決定する
173.3333... + 106.6666... + 80
それでも解決しない場合
再分配しても以下の様に、
0
を下回るmin-width
を下回る- margin, paddingを下回る
- コンテンツサイズを下回る
に当てはまる場合は、サイズが下回らない様にコンテナからはみ出し、分配はそこで終わりとなる。
flex
コンテナの幅が 360px
、
flex
アイテムの flex-shrink
が 1
、 2
、 3
、
width
が 0px
、
左右の padding
が 70px
に設定されていた場合、下のスクショの様な結果になる。
左右の padding
の合計 140px
より縮小することが出来ないため、 flex コンテナからはみ出る。
追記(2016年9月6日)Firefoxの表記について
Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis
flexboxのプロパティの組み合わせを比較できるカタログが欲しいなーと思ったので作ってみました。
各プロパティーの値がどのように解釈されるか、ブラウザ間での違いなどが分かります。
あれもこれもと入れていたら無駄に長くなりました。
2017年8月17日追記
Safari の flex-basis
の挙動が Chrome、Firefox、Edge と同じなっていたので、記事とサンプルをアップデートしました。
See the Pen Case Studies of Flexbox by Keita Okamoto (@all-user) on CodePen.
縦のストライプの幅が10px
、flexコンテナの幅が360px
となっていて、flexアイテムとそのコンテンツの大きさはそれぞれのラベルに表示されます。
コンテナの上には当てているスタイルの要約と最終的なサイズを割り出す計算式があります。
全ブラウザで結果が同じ場合は緑、ブラウザ間で結果に差異がある場合は赤の文字になっています。
一応仕様的にはこうなるべきだろうという計算式を書いているつもりですが、解釈が間違っているかも知れません。
なんか違うなと思ったら教えていただけると助かります。
ベストプラクティス(暫定)
flexboxの各アイテムが最終的にどのサイズで描画されるのかは領域の分配ルールに様々な要素が絡んでいます。
そのため、適当にプロパティを組み合わせていくと、なかなか思い通りのサイズになりません。
スコープを絞るために話をflex-direction: row
に限定するとして、サイズの計算に影響を与えるプロパティはこれだけあります。
flex-shrink
flex-grow
flex-basis
width
min-width
max-width
margin
padding
box-sizing
さらにブラウザ間の誤差やflexboxの抱える様々なバグを考慮して全てをコントロールするのはかなり難しいと思われます。(この実験に関して言えば、実際にはブラウザの誤差はあるひとつの挙動に起因していたため、そこまで辛くなかった。後述)
色々試した結果、暫定的なベストプラクティスとして以下のルールを守るのが良さそうだという結論になりました。
flex-basis
はauto
(初期値)width
を0
に設定する(ただしflex-direction: column
+height
の時は要注意。下に追記あり)
width
を0
以外にしたい場合はmin-width
をそれより小さい値で設定する
min-width
を設定すればflex-basis
でもブラウザ間の差異は無くなりますが、あえてflex-basis
を使うメリットも特に無さそうなのでwidth
を使うのが良いかなと思ってます。
- flex-basisとmin-widthを組み合わせた例
あくまで厳密にサイズをコントロールしたい時のベストプラクティスです。(flexboxでテーブルみたいに縦を揃えたい、とか)
コンテンツのサイズが予測できたり、サイズに応じてよしなにやって欲しい時はwidth
、flex-basis
ともにauto
(必要に応じてmax-width
、min-width
)でも良いと思います。
幅の計算をするときに気をつけること
padding
、margin
の扱い
padding
、margin
はflex-shrink
、flex-grow
の影響を受けないようです。
常に設定された値になる必要があるので、スペースを分配した結果が設定した値を下回る場合はスペースを再分配する必要があります。
- 左右の
padding
80px
を下回るため再分配が行われる
box-sizing
の影響
padding
がwidth
に含まれるかどうかはbox-sizing
の値によって決まります。
初期値であるcontent-box
の場合は以下のようになります。
width: auto
のためコンテンツサイズ分の40px * 3
を除く240px
がflex-grow
の値に基いて分配される
コンテンツサイズ分の
40px * 3
に加え、左右のpadding
40px * 3
を除く120px
がflex-grow
の値に基いて分配される
flexアイテムの
width
160px * 3
とコンテナサイズ360px
の差-120px
がflex-shrink
に基いて分配される
flexアイテムの
width
160px * 3
に左右のpadding
80px * 3
を加えた720px
とコンテナサイズ360px
の差-360px
がflex-shrink
に基いて分配される
min-width
の効果
width
の設定を0
以上に設定していて、その値よりコンテンツサイズが小さい場合、flex-shrink
はコンテンツサイズを下回って縮小することができなくなります。(flex-basis
の場合は0
に設定してもコンテンツサイズを下回るサイズにはなれないことがある。後述)
min-width
を設定することで、その値まではコンテンツサイズを下回るサイズに縮小することが出来ます。
コンテンツサイズの
160px
を下回って縮小することが出来ない(Safari、IE11は縮小する)min-width: 0
によりコンテンツサイズの160px
を下回って縮小することが出来る
ブラウザ間の差異(2016年8月28日現在)
この実験に絞って言えばブラウザ間で結果に差異が出る原因は、flex-basis
がコンテンツサイズを保証するかどうか、ということに集約できました。
Chrome、Firefox、Edgeはコンテンツのサイズを維持。
Safari、IE11はコンテンツサイズを無視する。
コンテンツサイズを保証するというのはどういう事かというと、flex-shrink
やflex-grow
を適用した後に、もしflexアイテムのサイズがコンテンツサイズを下回っていた場合に、そのflexアイテム以外のflexアイテムの間でネガティブなスペースを再分配し、必ずコンテンツサイズを下回らない様にします。(それでも下回る場合はそれ以上縮小しない)
- 再分配してもコンテンツサイズを下回るのでそれ以上縮小しない例
どちらが仕様的に正しいのかはわかりませんが、IEを除くとコンテンツサイズを無視するのはSafariだけなのでChrome、Firefox、Edgeの動作がより最新の仕様に沿っているのかもしれません。
現在は Safari も Chrome、Firefox、Edge と同じ動作です。
追記(2016年8月31日)height
の0
指定について
flex-direction: column
時にheight
に0
を指定すると、たとえflexアイテムがflex-grow
によってコンテナいっぱいに広がっていたとしても、flexアイテム内の要素からはheight
が0
に見えてしまうらしく、%
指定が出来なくなるというデメリットがあることが分かりました。(Safariのみ)
height
の代わりにmin-height
を0
にすれば大丈夫でした。
- Safariのスクショ。左:
height: 0
、右:height: 100%
+min-height: 0
枠線付きがflexコンテナ、濃いグレーがflexアイテム、薄いグレーがflexアイテム内のブロック要素
http://codepen.io/all-user/pen/RGbKRd
追記(2016年9月5日)flex-basis
の content
について
flex-content
に指定できる値 content
についての調査を追加しました。
結論から書くとブラウザ間の挙動がバラバラ。
今はまだ使わないほうが良さそうです。
以下、実験で分かったこと。
ショートハンドが効かない(Chrome, Firefox, Safari, IE11)
ショートハンドで指定した flex-grow
、 flex-shrink
がなぜか無効になります。(Edge は有効)
flex-grow
は 0
(Chrome, Firefox, Safari, IE11)、 flex-shrink
は 1
(Chrome, Firefox, Safari)と解釈されているように見えます。
ロングハンドの指定は有効でした。
flex-grow
flex-basis
にcontent
flex-grow
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
、content
、auto
flex-grow
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
flex-grow
に1
、2
、3
をロングハンドで指定
flex-shrink
flex-basis
にcontent
min-width
に0
flex-shrink
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
、content
、auto
min-width
に0
flex-shrink
に1
、2
、3
をショートハンドで指定
flex-basis
にcontent
min-width
に0
flex-shrink
に1
、2
、3
をロングハンドで指定
追記(2016年9月6日)Firefoxの表記について
CSSの先頭に書くやつ
// Stylus * position relative
リセット系CSSと併用することを前提にしています。
使用するライブラリにbox-sizing
の設定がない場合は以下も追加します。
// Stylus :root box-sizing border-box * box-sizing inherit
box-sizing
に関してはsanitaze.css
を参考にしています。
Eric Meyer’s Reset CSS、normalize.cssなどにはbox-sizing
の設定は含まれていませんでした。
position
を設定しない時の問題
CSSのプロパティーの中にはposition
の値に依存しているものがいくつかあります。
position
の初期値はstatic
という値ですが、
MDNの説明では、
- static
規定の振る舞いです。フロー内の現在の位置に配置されます。top、right、bottom、left、z-index プロパティは適用されません。
MDN: position
とあります。
上記に加え、
overflow
scroll
、auto
に設定してもスクロールしないhidden
を設定してもはみ出した部分が表示される- つまりオーバーフローしていると見なされなくなる?
- 子要素の
width
、height
にパーセント指定static
に設定された要素は無視される- 親を辿って
static
以外に設定された要素の値から算出される
などの影響も受けます。
多分、他にもあると思います。
はっきり言って、あるプロパティーがうまく効かなかった時に、それがposition
のせいかどうか調べるのはめちゃくちゃしんどいです。
ていうかstatic
ってなんなんだほんとに。意味あんのか。
なので今は全ての要素にposition: relative
をあてています。
*
を使った指定は何かと問題も引き起こしやすいですが、このrelative
の指定に関してはメリットのほうが大きいかなと思います。