均等割付した折り返しFlexboxの最後の行

Flexboxを使って折り返せる横並びのリストを作ることが増えてきた。大抵うまくいくのだが、項目間に溝をうまく確保しようとjustify-content: space-betweenを使ってしまうと、最後の行でおかしくなる困ってしまう。具体的には3列以上の横並びで最後の行に2つ(以上列数より下)しか項目がない場合、上図のように最後の行だけ項目が左寄せになってくれない。

この状況を修正する方法はあまりなく、少なくともFlexboxモジュールでは提供されていない。

まず考えられるのが溝の制御にjustify-contentプロパティーを使わない方法だ。しかしこれは列と列の間だけに溝を作りたい場合にかなり面倒くさい。次にわかりやすい方法は擬似要素を使い項目を追加する方法だ。今のところ擬似要素は最大2つまでしか作れないため、3列か4列でしか機能しない(4列ではorderも組み合わせることになる)。5列以上となると空の項目をあらかじめ作っておき、高さ0の最後の行を別に作り出す方法がある。汎用的に機能するが、美しくない解決方法だ。


問題は行にある項目数によってspace-betweenアルゴリズムが溝に使う幅が変わってしまうことだ。つまり最後の列に来る項目数に応じて、最後の項目だけそのサイズを大きくしてやれば良さそうだ。列が可変だったり多くなるとややこしくなるので、擬似要素ではできない5列を想定して考えてみることとする。

See Demo: Justified Flexbox

擬似クラスは連結することで対象を絞ることができる。あまり使う機会はないが、このようなケースでは最後の要素を項目がいくつあるのかに応じて選択できるので役に立つ。今回やりたいことは項目数がちょうど5nまたは5n+1ではない時だ。例えば2余り(7や12や102など)の時、最後の要素は以下のようなセレクターで選択できる。

.item<mark>:nth-child(5n+2):last-child</mark> {
  margin-right: 18rem;
}

先頭から数えて5n+2番目かつ最後である項目ということになる。この要素のサイズを制御し、溝に使われる幅を調節してやっているわけだ。ここではmargin-rightプロパティーを使ったが、背景がベタ塗りでないならwidthプロパティーでも大丈夫だ。また擬似要素をここで使っても大丈夫だ。

.item:nth-child(5n+3):last-child {
  margin-right: 12rem;
}

.item:nth-child(5n+4):last-child {
  margin-right: 6rem;
}

同じように3余り(8や18、203など)と4余り(9や24、304)のためのルールセットも作ってやれば、5列には完全に対応することができる。更に同じように作った3列用や4列用、そして6–8列用くらいまでを作ってやれば、列数が変化する場合にも対応可能なはず(まだ作っていない)だ。そのため汎用的な解とすることができるだろう。

かなり簡略化したテストなので、幅や溝のサイズは固定で作成した。今のFlexboxを使えるならcalc()もだいたい使える(ダメなのはOpera Miniだけ)ため、計算式を考え出せればどのような幅と溝のパターンでも大丈夫だろう。


:nth-child()擬似クラスは意外に奥が深く、表を縞々に染めるためだけにあるわけではない。ちょっとしたアイディアを思いつくと、思ってもみない効果が得られる。CSSではないものはないのが常なので、トリッキーすぎない形での工夫を行う手段として:nth-child()擬似クラスも学んでおくと助かる場面はあるだろう。

本当はほぼグリッドなのでGrid Layoutモジュールを使いたい。これを機会に覚え……てもまだ使えないので、こういう手段を編み出す必要がある。