Flexbox、使ってますか?
僕はまだなんですが、そろそろflexboxを使ってグリッド・システムを作りたいと思っていて実験中です。今までのカラム幅の指定には%やpx値を使ってきましたが、flexboxでは%やpxを指定しない方法を考えています。しなくて良い計算はしたくないですからね。
ところが、検証の開始そうそうに「flexアイテム」の幅がどうやって計算されるのかわからなくて、はまってしまいました。調べてみたので情報をまとめておきたいと思います。
今回の検証はMacのChrome 52.0.2743.116 (64-bit)とFirefox 46.0.1で確認をしています。あと、Flexboxの基本を理解していることを前提に書いているので、基本については以下あたりをご参照ください:
- A Complete Guide to Flexbox | CSS-Tricks
- CSSレイアウトにfloatはもう古い! Webデザイナー初心者でも始められるFlexbox入門 – ICS MEDIA
目次
- まずは結論から — flexアイテムの幅の計算方法
- HTMLとCSSの記述例
- flexプロパティについて
- flexに数値を1つだけ指定した場合の解釈
- flex-basisに幅の指定がある場合の計算方法
- flexアイテムにpaddingの指定がある場合の計算方法
まずは結論から — flexアイテムの幅の計算方法
結論からざっくり言ってしまいます。
display: flex
を指定した親要素を「flexコンテナ」、その中にある子要素を「flexアイテム」と呼びます。
上図のようにスペースが余っている場合、この「flexアイテム」にはflex-grow
の指定にしたがって余ったスペースが分配される仕組みになっています。
下図のようにflex-grow
(またはflex
)の合計で余白スペースを分割してflex-grow
(またはflex
)で指定した通りに分配されるようになっています。下図ではflexアイテムにflex: 1
とflex: 2
が指定してあるので、余ったスペースは3分割され、それぞれのflexアイテムに分配されます。
ちなみに、後で説明しますがflex
は複数のプロパティのショートハンドです。
HTMLとCSSの記述例
とりあえず、上図のように2カラムを1 : 2の幅でレイアウトする際のサンプル・コードを見てみてください。
HTMLの例
<div class="flex-container">
<div class="flex-1">
<p>Flex 1</p>
</div>
<div class="flex-2">
<p>Flex 2</p>
</div>
</div>
CSSの例
.flex-container {
display: flex;
}
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
こんなに簡単に思った通り1 : 2のカラム・レイアウトができてしまうわけですね。
これは先日見かけたPen で使われていた方法なんですが、「なぜ?」の部分が理解できませんでした。
まず、flex: 1
ってなに?
なんで値が1つでいいの? …という初歩的なところから疑問でした。
flexプロパティについて
調べてみたら、flex
プロパティは以下の3つのプロパティのショートハンドで、flex-shrink
とflex-basis
は省略できるんですね。
- flex-grow
- flex-shrink
- flex-basis
で、flex
の初期値(initial)はflex: 1 0 auto
ということで、省略した値は初期値になるのかと思ったら違いました。
W3CのCSS Flexible Boxの仕様 を読んだら、flex
に1つだけ数値を指定すると、以下のようにflex-shrink
は1
に、flex-basis
は0
になるそうです。初期値(initial)はflex: 1 0 auto
となっていたので混乱しました。省略された場合の値と初期値は別物なんですね。
flex: <number> 1 0;
仕様 にはflex: initial
、flex: auto
、flex: none
についてもしっかり説明があるので確認しておくと良いかもしれません。
注意: IE10-11のバグについて
IE10にはバグ があって、flex: 1
はflex: 1 0 0px
と解釈されてしまうとのことです(IE11では修正されたとのことです)。さらに、IE10-11では、flexショートハンドで記述した際にflex-basisの値に単位をつけないと正確に認識されないというバグ があるそうです。これを避けたかったら省略せずにflex: 1 1 0%
のように0でも%を記述しなくてはならないようです。
flexに数値を1つだけ指定した場合の解釈
ということで、さっきのflex: 1
はflex: 1 1 0
と同じということになります。
- flex-grow: 1
- flex-shrink: 1
- flex-basis: 0
flexアイテムの幅を0をベースに計算して、flexコンテナ内に余白がある場合、余白となる幅をコンテナ内にある子要素のflex-grow
の合計で割った値を1ユニットとして、それがflex-grow
の値にしたがって振り分けられます。
たとえば、flexコンテナの幅が900px
の場合、flexアイテム「A」のflex-grow
の値が1
、flexアイテム「B」のflex-grow
の値が2
だったとします。その場合、以下のような計算になります。
- Aの幅 = A自体のコンテンツ幅 + (1 × (900px – AとBのコンテンツ幅の合計) ÷ 3)
- Bの幅 = B自体のコンテンツ幅 + (2 x (900px – AとBのコンテンツ幅の合計) ÷ 3)
さっきの図の通りですね。
ちなみに、先ほどのCSSの例の場合、flex-1
とflex-2
のどちらもflex-basis
が0
になり、幅が0で計算されます。わかりやすいように、flexコンテナの幅が900px
の場合で計算してみます。
- flex-1の幅 = 0 + (1 × (900px – 0) ÷ (1 + 2)) = 300px
- flex-2の幅 = 0 + (2 × (900px – 0) ÷ (1 + 2)) = 600px
(1 + 2)のところは、flex-1
とflex-2
に指定されたflex-grow
の値の合計です。
無事に1 : 2のカラムが構築されているのがわかりましたね。
flex-basisに幅の指定がある場合の計算方法
念のためflex-basisに幅が指定されている場合でも同じように1 : 2のカラムが構築できるか計算をしてみます。例えば、.flex-1
には150px
、.flex-2
には300px
を指定してみます。
.flex-1 { flex: 1 1 150px; }
.flex-2 { flex: 2 1 300px; }
- flex-1の幅 = 150px + (1 × (900px – (150px + 300px)) ÷ (1 + 2)) = 300px
- flex-2の幅 = 300px + (2 × (900px – (150px + 300px)) ÷ (1 + 2)) = 600px
flex-1 : flex-2 = 1 : 2の幅になりますね。
ただ、これはレイアウトが1 : 2になるように幅の値(150 : 300 = 1 : 2)を指定した結果です。以下のように違う比率の値を指定すると、違った比率のレイアウトになります。たとえば、.flex-1
には420px
、.flex-2
には180px
を指定してみます。
.flex-1 { flex: 1 1 420px; }
.flex-2 { flex: 2 1 180px; }
- flex-1の幅 = 420px + (1 × (900px – (420px + 180px)) ÷ (1 + 2)) = 520px
- flex-2の幅 = 180px + (2 × (900px – (420px + 180px)) ÷ (1 + 2)) = 380px
幅の大きさも比率も、全然違うレイアウトになりますね。
flex-grow
はあくまで余白の分配方法を指定する数値なので、flex-basis
で指定した幅との組み合わせで、それぞれのflexアイテムの幅が計算されていることがわかります。
だいぶflexアイテムの幅の計算方法がわかってきました。
flexアイテムにpaddingの指定がある場合の計算方法
では、今度はflexアイテムにpadding
が指定されている場合はどうでしょうか?
これは以下の2つの要因によって計算が違ってきます:
- flex-basisの幅の指定
- box-sizingの値
flex-basisに幅の指定がありbox-sizingの指定がない場合
flex-basis
に幅が指定されていてbox-sizing
の指定がない場合(box-sizing: content-boxの場合)、flex-1
とflex-2
のそれぞれのコンテンツ幅(150pxと300px)にpadding
(10px × 2)が含まれないため、余白の計算にpadding
分の数値を足して計算されるようです。
- flex-1の幅 = 150px + (1 × (900px – (150px + 20px + 300px + 20px)) ÷ (1 + 2)) = 286.67px
- flex-2の幅 = 300px + (2 × (900px – (150px + 20px + 300px + 20px)) ÷ (1 + 2)) = 573.33px
図にすると、以下のようになります。
flex-basisに幅の指定があり、box-sizing: border-boxの指定がある場合
flex-basis
に幅が指定されていて、なおかつbox-sizing: border-box
を指定した場合、コンテンツの幅にpadding
が含まれるため、余白の計算の調整が必要ありません。そのため、計算はpadding
がない時と同じになります。
- flex-1の幅 = 150px + (1 × (900px – (150px + 300px)) ÷ (1 + 2)) = 300px
- flex-2の幅 = 300px + (2 × (900px – (150px + 300px)) ÷ (1 + 2)) = 600px
計算がシンプルになっていいですね。
flex-basisに幅の指定がない場合
さらに今度は、flex-basis
の指定がない場合(または0が指定されている場合)の計算はどうなるのか見てみます。これはbox-sizing
の指定のあり・なしに関係なく、同じ計算結果になります。(厳密に言うと、paddingが幅に含まれるか含まれないかでflexアイテムの幅はpadding分だけ異なりますが、見た目上の結果は同じになります。)
- flex-1の幅 = 0 + (1 × (900px – (10px × 4)) ÷ (1 + 2)) = 286.67px
- flex-2の幅 = 0 + (2 × (900px – (10px × 4)) ÷ (1 + 2)) = 573.33px
flex-basis
で幅を0
に指定しているのに、不思議な計算な気もしますけど。そういう仕様だということでしょうか?
「余白を計算する」という意味で、コンテンツに幅が発生する限り、その調整を行うということなのでしょうか?幅が0の要素に対するpadding
の論理上の扱いもbox-sizing: border-box
と合わせて考えると謎なような気もしますが。実際にHTMLとCSSを組んでみて、ブラウザで確認すると上に書いた計算結果と同じになります。
注意: IE10-11でのバグについて
IE10-11でflex-basis
に指定した幅にbox-sizing: border-box
が効かないというバグ があるそうです。これを回避するには、以下のHTML例のようにflexアイテムの中の要素にpadding
を指定するのが良さそうです。
HTMLの例
<div class="flex-container">
<div class="flex-1">
<p>Flex 1</p>
</div>
</div>
CSSの例
.flex-container {
display: flex;
}
.flex-1 { flex: 1; }
.flex-1 p { padding: 10px; }
まとめ
これでようやくFlexboxを使ったグリッドシステムが作れそうな気がしてきました。Flexboxの特性を活かしたうえで、よりシンプルで柔軟なシステムを構築するヒントが得られたような気がしています。
Bootstrap 4 やFoundation 6 のようなメジャーなフレームワークでも導入されてますし、これからグリッドシステムを構築するならFlexboxで間違いないんだと思いますが。すべて人任せというのも納得がいかないので、せめて検証くらいは自分でしておこうと思います。
Flexboxの仕様はCRの状態 ですし、まだバグ も隠れていそうです。また、今回の検証は最新版のMac ChromeとFirefoxでしか確認していないので、まだ現場での実装には注意が必要だと思っています。でも、他のブラウザでテストしてポリフィルなどの実装が確認できたら、そろそろGOしちゃっても良さそうですね。
Flex GO!
コメントを残す