flexでwidthを指定しているのに揃わない原因

※本ブログの目的は個人の備忘録であり、コードは参考用として掲載しています。
実際に使用される際は、ご自身の環境で十分に動作確認を行ってください。
コードの利用によって生じたいかなる問題についても責任を負いかねますので、あらかじめご了承ください。

flexレイアウトで横並びを作る際、widthを指定しているにもかかわらず、幅が揃わないことがあります。

検証ツールで確認すると確かにwidthが当たっているのに、見た目が揃わない。

このような場合、原因としてよくあるのが「flex-basisとwidthの優先順位」です。

flexでは、通常のブロックレイアウトとは幅の扱いが少し異なります。

flexではwidthよりflex-basisが優先される

flexアイテムの幅を決める際、基本サイズとして参照されるのはwidthではなくflex-basisです。

flexの初期値を理解する

flexレイアウトの仕様について思い出してみましょう。

親要素にdisplay: flex;を指定すると、その直下の子要素はflexアイテムとして扱われます。

flexアイテムには初期値として

flex: 0 1 auto;

が適用されています。

  • flex-grow: 0
  • flex-shrink: 1
  • flex-basis: auto

つまり、

  • 伸びない
  • 縮む
  • 基準サイズはauto(=通常はwidthを参照)

つまり、flex-shrink: 1; が初期値のため、合計幅がコンテナを超えた場合は自動的に縮小計算が行われます。

また、この指定でよく使われるのがflex: 1;です。flex: 1;は省略記法で、実際には次のような値になります。

flex: 1 1 0%;

それぞれの意味は以下の通りです。

  • flex-grow: 1
  • flex-shrink: 1
  • flex-basis: 0%

重要なのは、flex-basisが0%になっている点です。

つまり、width: 33.333%;を指定していても、flexレイアウト上では「基準サイズは0%」として扱われます。

flex-basisが0%になると、各要素は0を基準サイズとして扱われ、その後flex-growに応じて空きスペースが分配されます。(ただし、min-widthなどの制約がある場合はその限りではありません。)

以下はMDNのflex-basisの説明からの引用です。

もし flex-basisauto 以外の値に設定されていて、同じフレックスアイテムに widthflex-direction: column の場合は height)が設定されている場合は、flex-basis の値が優先されます。

つまり、flex-basisが0%などauto以外の値になっている場合、widthは基準サイズとしては参照されません。

なぜ合わないことがあるのか

flex-basisが0%になると、各要素は「いったん幅0として扱われる」状態からスタートします。

その後、flex-growに従って余白が分配されますが、次のような条件があると要素ごとの差が生まれます。

これらの差があると、flexの再計算時に各要素の縮み方や広がり方に差が出ます。

  • コンテンツ量が違う
  • 折り返せないテキストが含まれている
  • 画像サイズが異なる
  • min-widthの影響を受けている

結果として、1つだけわずかに広がる、あるいは縮まない、といった現象が起きます。

widthを使うならflexを整理する

もしwidthを使いたい場合は、flex側を次のようにします。

.item {
  width: 33.333%;
  flex: 0 0 auto;
}

先程の引用にも書かれていましたが、autoにすると、widthがそのまま基準サイズとして扱われます。

設計の考え方

flexレイアウトでは、「widthで幅を決める」という従来の感覚がそのまま通用しない場面があります。

重要なのは次の点です。

  • 幅の基準はflex-basisである
  • flexの省略記法は内部的な値を理解して使う
  • widthとflexを同時に書く場合は、どちらを基準にしたいのか明確にする

flexで幅が揃わないときは、まずflex-basisの値を確認することが有効です。

検証ツールで「Computed」タブを見ると、実際にどの値が使われているか分かるので確認してみましょう。

まとめ

flexレイアウトでは、widthはそのまま最終的な幅になるとは限りません。

親要素にdisplay: flex;を指定した時点で、子要素は

flex: 0 1 auto;

という初期値でレイアウト計算に参加します。
さらにflex: 1;を指定すると、

flex: 1 1 0%;

となり、基準サイズはwidthではなくflex-basis(この場合は0%)になります。

flexでは、

  1. まずflex-basisを基準にサイズが決まり
  2. 余白があればgrowで広がり
  3. 足りなければshrinkで縮み
  4. その際min-widthなどの制約も考慮される

という流れで最終サイズが決定されます。

そのため、「widthは指定しているのに揃わない」という現象は、widthが無効なのではなく、その後に行われるflexの再計算によって結果が変わっている状態です。

flexで幅が揃わない場合は、まず次の点を確認します。

  • flex-basisは何になっているか
  • flex-shrinkが有効になっていないか
  • min-widthの制約を受けていないか

flexは便利ですが、幅の決まり方はブロックレイアウトとは異なります。

値そのものではなく、どの順序で計算されるのかを理解することが大切です。

参考

flex-basis - CSS | MDN