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
をロングハンドで指定