CSS:Flexで任意の数で折り返す、あるいは横Masonry

ギャラリーの様な縦横比ばらばら、数もその時その時で違う、そんな画像達を綺麗に並べたい、という場面は多々あると思います。

そんな時どうすれば一番綺麗に並べられるかを考えました。

一番簡単にそれっぽくするにはMasonryかと思います。
こんな感じでcolumn-countに任意の段の数値を入れればすぐにできます。

See the Pen Masonry by Y38design (@y38) on CodePen.

See the Pen Masonry by Y38design (@y38) on CodePen.

簡単にギャラリー様にできるcolumn-countですがデメリットがあります。
まずは
1.配置が左から縦に並ぶこと
2.下端が揃わない
3.画像の数によらずcolumn-countに指定した段数を守るので画像の枚数が少ないと段すべてを埋めることができない。
というのが挙げられます。

個人的には1.は画像ギャラリーの場合は特に気にしませんが問題は2.と3.です。
2.はMasonryはそういうものだとは思いますが気に食わないです><
この下端が揃わない状態を回避したい。
最大の問題は3.。上のcodepenの下のように画像が少ないのに段数を守ってしまい右が余ってしまいます。
これはなんとしても避けたい。

ということで考えたのが、Flexで任意の倍数で折り返すことができないかというもの。
ざざっと検索したところではいい解決方法は見つからず、自力でなんとかすることに。

考えた理屈としては、js(jquery)を使い、
1.折り返したい倍数の後ろになにがしかの要素を追加する
2.その追加した要素にwidth:100%、flex-shrink:0を指定し折り返す。
 その時height:0を指定すれば見た目にはこの要素は見えず単に折り返した様に見える
3.画像の幅 / 高さの数値をcssのflex-growに割り当て、画像の高さ / 幅の値をflex-shrinkに割り当てる
そうすれば画像の高さが揃い、その中で縦横比を守ったまま自由に幅が伸び縮みするのではないか、と考えました。

それが以下。

See the Pen Ratio_0 by Y38design (@y38) on CodePen.

See the Pen Ratio_0 by Y38design (@y38) on CodePen.

cssでulをflexにし、wrapさせる。
jsで画像の縦横比からそれぞれにflex-growとflex-shrinkを指定する。
同じくjsで折り返したい倍数の後ろに.breakをinsertAfterで追加する。
cssで.breakにwidth:100%; flex-shrink:0; height:0を指定する。

これでだいたいうまくいきました。
cssでliにflex-grow:100;としているのは、jsとcssの読み込み順によってはjsで指定するflex-growが効かなかったからです。

ただこれでは問題があります。
以下の様に倍数の余りが少ない時、その分の画像が大きくなりすぎ、画像の全体が見えなくなってしまいます。

See the Pen Ratio_1 by Y38design (@y38) on CodePen.

See the Pen Ratio_1 by Y38design (@y38) on CodePen.

これをどう解決するか考えた結果、
1.ulの中の要素の数を取得する
2.その数を折り返す数値で割り、余りが多ければそのまま、余りが少なければ折り返す位置の数を変更する
という考えに辿り着きました。

それが以下

See the Pen Ratio_2 by Y38design (@y38) on CodePen.

See the Pen Ratio_2 by Y38design (@y38) on CodePen.

今までと同じ様に基本は6の倍数で折り返します。
ただし、liの要素の数を6で割った時の余りが少ない時(ここでは1、2)は、折り返しの倍数を変更(ここでは4)します。
ここのjsのif文はもっとちゃんとした書き方があるかもしれません…

また、これだけだと1px単位まできっちり縦横埋めてはくれないので、imgにtransform:scale(1.15)とほんの少〜しだけ大きくしliにoverflow:hiddenではみ出す部分を隠す、ということをしています。(codepenを埋め込むと表示が小さくなりすぎるためscaleを1.15にしていますが、実際には1.05程度で大丈夫でした)

これで、画像の高さを揃え、幅は可変、親要素を埋め尽くす、という見た目にできました。
横並びのMasonryとか、flexを任意の場所で折り返す、みたいなものです。

理屈を思いつくのはすぐだったのですが、jsのことを解ってないので実際に動く様にするのには手こずりました。
でも思いつきを実現できて満足です。

この手法は画像の場合ではうまくいきますが、
文字だけや文字と画像の混在などの場合はうまくいかないことが予想されます。
あくまで画像ギャラリー用として、参考になればと思います。