Flexboxを使うなら知っておきたい「flexアイテム」の幅の計算方法

Advertisement

how-flox-grow-works

Flexboxを使っていて「なんでこの幅になるんだろう?」と疑問に思ったことはありませんか?

僕はFlexboxを使い始めたころ「flexアイテム」の幅がどうやって計算されるのかわからなくて、レイアウトにはまってしまいました。調べて整理したのでシェアします。

仕組みをしっかり理解しておくと、問題が起こったときに対処しやすくなります。ということで、flexアイテムの幅の計算方法、行ってみましょ〜!

まずは結論から — flexアイテムの幅の計算方法

結論からざっくり言ってしまいます。

display: flexを指定した親要素を「flexコンテナ」、その中にある子要素を「flexアイテム」と呼びます。

flex-container-and-item

上図のようにスペースが余っている場合、この「flexアイテム」にはflex-growの指定にしたがって余ったスペースが分配される仕組みになっています。

下図のようにflex-grow(またはflex)の合計で余白スペースを分割してflex-grow(またはflex)で指定した通りに分配されるようになっています。下図ではflexアイテムにflex: 1flex: 2が指定してあるので、余ったスペースは3分割され、それぞれのflexアイテムに分配されます。

flex-width-calculation

ちなみに、後で説明しますが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-shrinkflex-basisは省略できるんですね。

  1. flex-grow
  2. flex-shrink
  3. flex-basis

で、flexの初期値(initial)はflex: 0 1 autoflex: 1 0 auto)ということで、省略した値は初期値になるのかと思ったら違いました

W3CのCSS Flexible Boxの仕様 を読んだら、flexに1つだけ数値を指定すると、以下のようにflex-shrink1に、flex-basis0になるそうです。初期値(initial)はflex: 0 1 autoflex: 1 0 auto)となっていたので混乱しました。省略された場合の値と初期値は別物なんですね。

flex: <number> 1 0;

仕様 にはflex: initialflex: autoflex: noneについてもしっかり説明があるので確認しておくと良いかもしれません。

注: 初期値(initial)の記述に誤りがありました

上述の初期値(initial)ですが、正しくはflex: 0 1 autoだそうです。上の文章では値を修正してあります。

8月31日現在の最新の仕様のThe ‘flex’ shorthandの青い表 ではinitial1 0 autoと書いてあるのですが、その下の文章中では「The initial values of the flex components are equivalent to flex: 0 1 auto.」とあり、こちらが正しい値のようです。さらに混乱させてしまってすみませんでした。

@JForgさんのこちらのTweet で知りました。ありがとうございました!

[追記: 2016/08/31 06:30]

注意: IE10-11のバグについて

IE10にはバグ があって、flex: 1flex: 1 0 0pxと解釈されてしまうとのことです(IE11では修正されたとのことです)。さらに、IE10-11では、flexショートハンドで記述した際にflex-basisの値に単位をつけないと正確に認識されないというバグ があるそうです。これを避けたかったら省略せずにflex: 1 1 0%のように0でも%を記述しなくてはならないようです。

Advertisement

flexに数値を1つだけ指定した場合の解釈

ということで、さっきのflex: 1flex: 1 1 0と同じということになります。

  1. flex-grow: 1
  2. flex-shrink: 1
  3. 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)

さっきの図の通りですね。

flex-width-calculation

ちなみに、先ほどのCSSの例の場合、flex-1flex-2のどちらもflex-basis0になり、幅が0で計算されます。わかりやすいように、flexコンテナの幅が900pxの場合で計算してみます。

  • flex-1の幅 = 0 + (1 × (900px – 0) ÷ (1 + 2)) = 300px
  • flex-2の幅 = 0 + (2 × (900px – 0) ÷ (1 + 2)) = 600px

(1 + 2)のところは、flex-1flex-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-1flex-2のそれぞれのコンテンツ幅(150pxと300px)にpadding(10px × 2)が含まれないため、余白の計算にpadding分の数値を足して計算されるようです。

flex-with-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-with-padding-calculation

flex-basisに幅の指定があり、box-sizing: border-boxの指定がある場合

flex-basisに幅が指定されていて、なおかつbox-sizing: border-boxを指定した場合、コンテンツの幅にpaddingが含まれるため、余白の計算の調整が必要ありません。そのため、計算はpaddingがない時と同じになります。

flex-with-padding-box-sizing-border-box
  • 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-with-padding-wo-flex-basis
  • 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; }

確認に使ったブラウザ

この記事を書くために行ったブラウザでの確認は、MacのChrome 52.0.2743.116 (64-bit)とFirefox 46.0.1で行いました。

まとめ

これでようやくFlexboxを使ったグリッドシステムが作れそうな気がしてきました。Flexboxの特性を活かしたうえで、よりシンプルで柔軟なシステムを構築するヒントが得られたような気がしています。

Bootstrap 4 Foundation 6 のようなメジャーなフレームワークでも導入されてますし、これからグリッドシステムを構築するならFlexboxで間違いないんだと思いますが。すべて人任せというのも納得がいかないので、せめて検証くらいは自分でしておこうと思います。

Flexboxの仕様はCRの状態 ですし、まだバグ も隠れていそうです。また、今回の検証は最新版のMac ChromeとFirefoxでしか確認していないので、まだ現場での実装には注意が必要だと思っています。でも、他のブラウザでテストしてポリフィルなどの実装が確認できたら、そろそろGOしちゃっても良さそうですね。

Flex GO!

更新情報

  • イントロ部分の情報が古くなっていたので更新しました(2020/02/02)

About the author

Rriverのステッカーが貼られたMacBookの向こうにいる自分のMemojiの似顔絵

「明日のウェブ制作に役立つアイディア」をテーマにこのブログを書いています。アメリカの大学を卒業後、ボストン近郊のウェブ制作会社に勤務。帰国後、東京のウェブ制作会社に勤務した後、ウェブ担当者として日英バイリンガルのサイト運営に携わる。詳しくはこちら

ウェブ制作・ディレクション、ビデオを含むコンテンツ制作のお手伝い、執筆・翻訳のご依頼など、お気軽にご相談ください。いずれも日本語と英語で対応可能です。まずは、Mastodon @[email protected] Twitter @rriver 、またはFacebook までご連絡ください。

“Flexboxを使うなら知っておきたい「flexアイテム」の幅の計算方法” への8件のフィードバック

  1. […] Flexboxを使うなら知っておきたい「flexアイテム」の幅の計算方法 – Rriver […]

  2. […] 6. CSS:Flexboxを使うなら知っておきたい「flexアイテム」の幅の計算方法 この記事をRTする […]

  3. ナカヤマ より:

    大変有用な記事をありがとうございます!

    下記の式ですが積・商の記述順が逆になっていると思いました。
    flex-1の幅 = 0 + (1 × (900px – 0) ÷ (1 + 2)) = 300px
    flex-2の幅 = 0 + (2 × (900px – 0) ÷ (1 + 2)) = 600px
    flex-2の幅は、この計算だと900となってしまいます。

    正しくは
    flex-1の幅 = 0 + (1 × ((900px – 0) ÷ (1 + 2))) = 300px
    flex-2の幅 = 0 + (2 × ((900px – 0) ÷ (1 + 2))) = 600px
    または
    flex-1の幅 = 0 + (900px – 0) ÷ (1 + 2) × 1 = 300px
    flex-2の幅 = 0 + (900px – 0) ÷ (1 + 2) × 2 = 600px
    となるかと思いました。

    重箱の隅をつつくようで恐縮ですが、
    拝読していて気がつきましたのでお伝えいたします。

    • ryo より:

      ご指摘ありがとうございます。
      計算は間違っていないですが、意味を考えるとコメントいただいたものの方がわかりやすいですね。

      > flex-1の幅 = 0 + (900px – 0) ÷ (1 + 2) × 1 = 300px
      > flex-2の幅 = 0 + (900px – 0) ÷ (1 + 2) × 2 = 600px

      ありがとうございます。

      • ナカヤマ より:

        1年越しで・・・
        何か間違ったツッコミをしていてすみません!!
        優しくご対応いただいており、大変恐縮です。
        今後とも陰ながら、記事楽しみにしております。有難うございました。

  4. 松井陽明 より:

    大変に参考になるブログをありがとうございます。記事の中の図を使った説明ですが、図は何のアプリを使用して描いているのでしょうか? 私もこれから始めるブログ記事でこんな風に作成できたら嬉しいので、良かったら教えてください。

    • ryo より:

      図の作成は、たしか、このブログでも紹介しているAffinity Designerだったと思います。現在キャンペーン中で90日間の無料トライアル と50%セールをやっているので、まだ使ったことがなかったらおすすめです。

      • 松井陽明 より:

        貴重な情報をありがとうございます、早速使ってみたのですが、とっても使いやすくて、重宝できそうです。 合わせて、教えていただきたいのですが、ブログ用に画像として書き出す時のサイズと、Affinitey Designer の新規作成の時のボードのサイズはどれを使えばいいのでしょうか? あとブログの中のHTMLとCSSの書き出しはキャプチャーツールですか? 何度も申し訳ないのですが、よろしくお願いいたします。