読者です 読者をやめる 読者になる 読者になる

メモを揉め

お勉強の覚書。

Case Studies in Flexbox - flex-grow, flex-shrink, flex-basis

flexboxのプロパティの組み合わせを比較できるカタログが欲しいなーと思ったので作ってみました。
各プロパティーの値がどのように解釈されるか、ブラウザ間での違いなどが分かります。

あれもこれもと入れていたら無駄に長くなりました。

See the Pen Case Studies of Flexbox by Keita Okamoto (@all-user) on CodePen.

縦のストライプの幅が10pxflexコンテナの幅が360pxとなっていて、flexアイテムとそのコンテンツの大きさはそれぞれのラベルに表示されます。

コンテナの上には当てているスタイルの要約と最終的なサイズを割り出す計算式があります。
全ブラウザで結果が同じ場合は緑、ブラウザ間で結果に差異がある場合は赤の文字になっています。

f:id:alluser:20160828194455p:plain f:id:alluser:20160828194518p:plain

一応仕様的にはこうなるべきだろうという計算式を書いているつもりですが、解釈が間違っているかも知れません。
なんか違うなと思ったら教えていただけると助かります。

ベストプラクティス(暫定)

flexboxの各アイテムが最終的にどのサイズで描画されるのかは領域の分配ルールに様々な要素が絡んでいます。
そのため、適当にプロパティを組み合わせていくと、なかなか思い通りのサイズになりません。

スコープを絞るために話をflex-direction: rowに限定するとして、サイズの計算に影響を与えるプロパティはこれだけあります。

  • flex-shrink
  • flex-grow
  • flex-basis
  • width
  • min-width
  • max-width
  • margin
  • padding
  • box-sizing

さらにブラウザ間の誤差やflexboxの抱える様々なバグを考慮して全てをコントロールするのはかなり難しいと思われます。(この実験に関して言えば、実際にはブラウザの誤差はあるひとつの挙動に起因していたため、そこまで辛くなかった。後述)

色々試した結果、暫定的なベストプラクティスとして以下のルールを守るのが良さそうだという結論になりました。

  • flex-basisauto(初期値)
  • width0に設定する(ただしflex-direction: column + heightの時は要注意。下に追記あり)
    f:id:alluser:20160828222041p:plain
  • width0以外にしたい場合はmin-widthをそれより小さい値で設定する
    f:id:alluser:20160828223132p:plain

min-widthを設定すればflex-basisでもブラウザ間の差異は無くなりますが、あえてflex-basisを使うメリットも特に無さそうなのでwidthを使うのが良いかなと思ってます。

  • flex-basisとmin-widthを組み合わせた例
    f:id:alluser:20160828223924p:plain

あくまで厳密にサイズをコントロールしたい時のベストプラクティスです。(flexboxでテーブルみたいに縦を揃えたい、とか)
コンテンツのサイズが予測できたり、サイズに応じてよしなにやって欲しい時はwidthflex-basisともにauto(必要に応じてmax-widthmin-width)でも良いと思います。

幅の計算をするときに気をつけること

paddingmarginの扱い

paddingmarginflex-shrinkflex-growの影響を受けないようです。
常に設定された値になる必要があるので、スペースを分配した結果が設定した値を下回る場合はスペースを再分配する必要があります。

  • 左右のpadding 80pxを下回るため再分配が行われる
    f:id:alluser:20160828223132p:plain

box-sizingの影響

paddingwidthに含まれるかどうかはbox-sizingの値によって決まります。
初期値であるcontent-boxの場合は以下のようになります。

  • width: autoのためコンテンツサイズ分の40px * 3を除く240pxflex-growの値に基いて分配される
    f:id:alluser:20160828231139p:plain

  • コンテンツサイズ分の40px * 3に加え、左右のpadding 40px * 3を除く120pxflex-growの値に基いて分配される
    f:id:alluser:20160828230822p:plain

  • flexアイテムのwidth 160px * 3とコンテナサイズ360pxの差-120pxflex-shrinkに基いて分配される
    f:id:alluser:20160828234942p:plain

  • flexアイテムのwidth 160px * 3に左右のpadding 80px * 3を加えた720pxとコンテナサイズ360pxの差-360pxflex-shrinkに基いて分配される
    f:id:alluser:20160828223132p:plain

min-widthの効果

widthの設定を0以上に設定していて、その値よりコンテンツサイズが小さい場合、flex-shrinkはコンテンツサイズを下回って縮小することができなくなります。(flex-basisの場合は0に設定してもコンテンツサイズを下回るサイズにはなれないことがある。後述)

min-widthを設定することで、その値まではコンテンツサイズを下回るサイズに縮小することが出来ます。

  • コンテンツサイズの160pxを下回って縮小することが出来ない(Safari、IE11は縮小する) f:id:alluser:20160829001831p:plain

  • min-width: 0によりコンテンツサイズの160pxを下回って縮小することが出来る
    f:id:alluser:20160829001849p:plain

ブラウザ間の差異(2016年8月28日現在)

この実験に絞って言えばブラウザ間で結果に差異が出る原因は、flex-basisがコンテンツサイズを保証するかどうか、ということに集約できました。

ChromeFirefox、Edgeはコンテンツのサイズを維持。
Safari、IE11はコンテンツサイズを無視する。

コンテンツサイズを保証するというのはどういう事かというと、flex-shrinkflex-growを適用した後に、もしflexアイテムのサイズがコンテンツサイズを下回っていた場合に、そのflexアイテム以外のflexアイテムの間でネガティブなスペースを再分配し、必ずコンテンツサイズを下回らない様にします。(それでも下回る場合はそれ以上縮小しない)

  • 再分配してもコンテンツサイズを下回るのでそれ以上縮小しない例
    f:id:alluser:20160828202921p:plain

どちらが仕様的に正しいのかはわかりませんが、IEを除くとコンテンツサイズを無視するのはSafariだけなのでChromeFirefox、Edgeの動作がより最新の仕様に沿っているのかもしれません。

追記(2016年8月31日)height0指定について

flex-direction: column時にheight0を指定すると、たとえflexアイテムがflex-growによってコンテナいっぱいに広がっていたとしても、flexアイテム内の要素からはheight0に見えてしまうらしく、%指定が出来なくなるというデメリットがあることが分かりました。(Safariのみ)

heightの代わりにmin-height0にすれば大丈夫でした。

  • Safariのスクショ。左: height: 0、右: height: 100% + min-height: 0
    f:id:alluser:20160830235946p:plain
    枠線付きがflexコンテナ、濃いグレーがflexアイテム、薄いグレーがflexアイテム内のブロック要素

http://codepen.io/all-user/pen/RGbKRd

追記(2016年9月5日)flex-basiscontent について

flex-content に指定できる値 content についての調査を追加しました。

結論から書くとブラウザ間の挙動がバラバラ。
今はまだ使わないほうが良さそうです。

以下、実験で分かったこと。

ショートハンドが効かない(Chrome, Firefox, Safari, IE11)

ショートハンドで指定した flex-growflex-shrink がなぜか無効になります。(Edge は有効)
flex-grow0Chrome, Firefox, Safari, IE11)、 flex-shrink1Chrome, Firefox, Safari)と解釈されているように見えます。

ロングハンドの指定は有効でした。

  • flex-grow
    • flex-basiscontent
      flex-grow123 をショートハンドで指定
      f:id:alluser:20160905233121p:plain
    • flex-basiscontentcontentauto
      flex-grow123 をショートハンドで指定
      f:id:alluser:20160905234039p:plain
    • flex-basiscontent
      flex-grow123 をロングハンドで指定
      f:id:alluser:20160905235419p:plain
  • flex-shrink
    • flex-basiscontent
      min-width0
      flex-shrink123 をショートハンドで指定
      f:id:alluser:20160906001341p:plain
    • flex-basiscontentcontentauto
      min-width0
      flex-shrink123 をショートハンドで指定
      f:id:alluser:20160906001658p:plain
    • flex-basiscontent
      min-width0
      flex-shrink123 をロングハンドで指定
      f:id:alluser:20160906003231p:plain

追記(2016年9月6日)Firefoxの表記について

FireFox から Firefox に変更しました。