🎉
高さをなるべく揃えてくれる React Masonry コンポーネントライブラリを作った
Masonry とは
サイズがまちまちなコンテンツをタイル状に並べるときに、縦を詰めるようにするレイアウト。
react-masonry-css では駄目なのか
react-masonry-css では要素の高さを計算しているわけではなく、単純に左上から順に並べている。この手法だと、ある程度の数が並ぶと各列の最後の要素の位置がずれてしまう。
つまり、
1 | 2 | 3 |
1 | 2 | |
1 |
なら次の要素は 3 の下に来ないといけないが単純に並べているだけなので
1 | 2 | 3 |
1 | 2 | 6 |
1 | 5 | |
4 |
となってしまう。これが積み重なるとスクロールし終えたときに高さがかなり違う。
拙作のライブラリでは
1 | 2 | 3 |
1 | 2 | 4 |
1 | 5 | 6 |
となる。
ただし、高さを計算する分重い。
実現方法
- 各要素の高さを ResizeObserver + getBoundingClientRect で取得する
- 取得した高さを元に、各列がなるべく同じ高さになるように並べる
- 並べ終わって再 render する際にも ResizeObserver に引っかかってしまってまた再計算となるととても重くなるので、再計算は throttle する
検討した CSS たち
display: grid
grid を細かく (1px とか) しまくって CSS だけでなんとかするアプローチ。
grid には行数制限があるため駄目だった。
flexbox (row)
各行の高さが一律になってしまう。
flexbox (column)
高さの計算が少しでも狂うと崩壊する。
flexbox (row + コンテナ複数)
DOM ツリーが変わってしまうため css transition が使えない。
position: absolute
高さの計算をミスると悲惨なことになるが column ほどではない。
最終的にはこれを使った。
番外
grid-template-rows: masonry;
さえくれば CSS で全部済むのでこんなライブラリは不要になる。
Discussion