flexboxでメディアクエリなしでレスポンシブ|CSS

メディアクエリやコンテナクエリを使わずにレスポンシブ的な動きになる書き方が紹介されていたので試してみる。

参考ソース

HTML

<form>
  <input type="text" placeholder="Name">
  <input type="email" placeholder="Email Address">
  <input type="submit" value="Subscribe">
</form>
form {
  display: flex;
  flex-wrap: wrap;
}
  
form > input {
  flex: 1 1 10ch;
  margin: .5rem;
}
    
form > input[type="email"] {
  flex: 3 1 30ch;
}

説明

ポイントは以下の部分

//inputへの指定
flex: 1 1 10ch;
//emailアドレスインプットへの指定
flex: 3 1 30ch;

なお、上記は省略した書き方であり、省略せずに書くと以下のようになる。

flex-grow: 1;
flex-shrink: 1;
flex-basis: 10ch;

それぞれのプロパティの意味は以下のとおり。

flex-grow子要素の伸びる比率
子要素の大きさ自体の比率ではなく、子要素が内包する要素の大きさを引いて余ったスペースをどう分け合うかの指定
flex-shrink子要素の縮む比率
flex-growと逆に、子要素が内包する要素の大きさを引いた結果スペースが足りない場合に、それをどう分け合うかの指定
flex-basis子要素のベースとなる幅の指定

いずれも十分に理解できていないプロパティだと思うので、ここで少し各プロパティについて整理してみた。

flex-grow

item1 : item2 : item3 = flex-grow:1 : flex-grow:2 : flex-grow:3の例。

See the Pen Untitled by kngsmym (@kngsmym) on CodePen.

itemが包含する要素(ここでは数字一文字)を10px、コンテナの幅が360pxだと仮定して計算すると、次のような計算式がなりたつ。

まず配分のもととなる「余ったスペース」は以下の通り。

( 360px - ( 10px + 10px + 10px ) ) = 330px

各アイテムのwidthはアイテムのflex-growの比率に応じて配分されるスペースにそのアイテムの内容物の大きさを足した値となるので、ここでは以下の通り。

item1 : 330px / ( 1 + 2 + 3 ) * 1 + 10px = 65px
item2 : 330px / ( 1 + 2 + 3 ) * 2 + 10px = 120px
item3 : 330px / ( 1 + 2 + 3 ) * 3 + 10px = 175px

このサンプルでは結果として見た目上のアイテムのサイズが概ね1:2:3になってはいるが、あくまでも内容物に基づいた計算が行われた結果であり、幅自体が1:2:3で定義されたということではない。

逆に考えるとflex-growを1ではない設定で使う場面がそうそう思い当たらない気がする。

flex-shrink

item1 : item2 : item3 = flex-shrink:1 : flex-shrink:2 : flex-shrink:3で、アイテムのwidthを120pxとした例。

See the Pen Untitled by kngsmym (@kngsmym) on CodePen.

コンテナ幅が120px ✕ 3 = 360pxを超えているとアイテムはすべて同じ大きさで表示されているが、360pxを下回ると足りない分をflex-shrinkの値に基づいた比率でアイテムの余白が削られる。例えばコンテナサイズを200pxにした場合の計算式は以下のようになる。

配分の元となる、「足りないスペース」は以下の通り。

120px * 3 - 200px = -160px

各アイテムの大きさは、アイテムサイズから足りないスペースをflex-shrinkの比に基づいて引いた値となるので、おおよそ以下に近い値となる。

item1 : -160px / ( 1 + 2 + 3 ) * 1 + 120px = 94px
item2 : -160px / ( 1 + 2 + 3 ) * 2 + 120px = 68px
item3 : -160px / ( 1 + 2 + 3 ) * 3 + 120px = 40px

codepenの実行されたHTMLでリサイズハンドルを動かしてコンテナの大きさを変えてみると想定どおりの値となっていることがわかるはず。

結局flex-growと同様に、わざわざ設定する場面ってどんな場面?という印象。

flex-basis

それぞれflex-basisに100pxを設定した例。

See the Pen flex-basis by kngsmym (@kngsmym) on CodePen.

アイテムはすべてwidth:100pxとなり、コンテナのwidthが300pxを下回ると、同じ割合で小さくなります。

さてここでポイントとなった記述について。

flex: 1 1 10ch;
〜
flex: 3 1 30ch;

//省略せずに書くと以下のような感じ
flex-grow: 1;
flex-shrink: 1;
flex-basis: 10ch;
〜
flex-grow: 3;
flex-shrink: 1;
flex-basis: 30ch;

元のソースだと余計なスタイルや単位がわかりづらいので、自分用に簡略化したものが以下。

See the Pen responsive without media query by kngsmym (@kngsmym) on CodePen.

元のソースではinputで実装されており、試しにdivでもやってみた。

inputの場合は横幅により4つのパターンに変化する。divの場合それが3パターンに留まる。

流れとしてはまずすべての要素が1行に収まるかどうかがflex-basisの値で判断され、収まらない場合には改行が起こるが、改行されて1行に要素が一つとなった場合にその要素は横100%になるという挙動になっている。

inputの場合にはデフォルトスタイルがあたっており、かつtypeによって大きさが異なる(type=buttonは小さく配置される仕様?)などの理由から、flex-basisの各要素の合計値のあたりの挙動に微妙なずれが生まれ、1つ目の要素と2つ目の要素の比がコンテナサイズによって微妙に変化して、4パターンのレイアウトが実現している。

divでの実装の場合にはそれが起こらないので、flex-basisに基づいて1行に収まらなくなる幅未満になった時点で3つ目の要素が折り返すパターンとなり、その後3つの要素が縦に並ぶパターンの3パターンとなる。

実際に使うときには色々調整して意図に沿うものとするか、素直にメディアクエリで管理してやるのがいいような気がする。