高さの違うコンテンツを"いい感じ"に動的にグリッド表示したい! ライブラリ無しで!

ハローワールド。

[Masonry](https://masonry.desandro.com/), [magic grid](https://github.com/e-oj/Magic-Grid), etc...。みなさんはこのライブラリを知っていますか?
これらは、グリッドレイアウトを実現するためのライブラリです。

通常、CSSのflexboxやgridを使えばグリッドレイアウトは可能ですが、これらのライブラリは高さの違うコンテンツを"いい感じに"表示してくれます。

例えば、Magic Gridであれば以下のようにフレキシブルなグリッドレイアウトを実現してくれます。(以下のGifはMagic GridのGithubのReadmeより引用)
![Magic Grid Sample.gif](https://camo.githubusercontent.com/c81d42594b1b2fccd509ff83e8872f20fcd1de8a/687474703a2f2f64726976652e676f6f676c652e636f6d2f75633f6578706f72743d766965772669643d3137324553505a447751496637764c4d656c756e2d5f34526157445f2d6a39342d)

Flexboxでグリッドレイアウトを作ると高さが一定になってしまうため妙な余白ができてしまいます。
かといってGridを利用するときには事前に高さとか配置とか決めておく必要があるので動的に表示するのはなあ...ということで、こういったライブラリを利用していました。

しかし、これらのライブラリもメンテナンスがされてなかったり、TypeScriptのtypeがなかったり、実装を見るとleftとtopやtransformで座標を指定したりと手放しで喜んで利用したい、というライブラリではありませんでした(個人の感想です)。

なので、今回ライブラリ無しで、できる限りCSSだけでこれを再現したかったので投稿いたしました。
実際にはCSSだけではできませんでしたが。

**もっと簡単にできるなら教えて下さい(切実)**

## 完成品

<p class="codepen" data-height="465" data-theme-id="default" data-default-tab="result" data-user="sa2taka" data-slug-hash="zYxOJWX" style="height: 465px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Card Grid Layout without Library">
  <span>See the Pen <a href="https://codepen.io/sa2taka/pen/zYxOJWX">
  Card Grid Layout without Library</a> by sa2taka (<a href="https://codepen.io/sa2taka">@sa2taka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## 説明

上記の動作はCSSのGridを利用して、ほぼ無理やり実現しています。

### css

cssでは大したことはやってないのであまり詳しい説明はいらないかもしれません(jsもそうですが)

#### #grid

まず最初に上記のhtmlでは

``` html
<div id="grid">
</div>
```
となっている部分です。このgridは以下のスタイルが適用されています。

```css
#grid {
  display: grid;
  grid-auto-rows: 20px;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
}
```

`display: grid`の説明はいいとして、`grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))`ですが、詳細はMDNに任せるとして([grid-template-columns](https://developer.mozilla.org/ja/docs/Web/CSS/grid-template-columns), [repeat](https://developer.mozilla.org/ja/docs/Web/CSS/repeat))、結果的に言えばウィンドウの横幅に合わせて列数を"いい感じ"にフィットさせてくれるやつです。これのおかげで随分フレキシブルな動きが可能です。

今回重要となるのが`grid-auto-rows`です。[MDN](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout#The_implicit_and_explicit_grid)によると、

> grid-auto-rows と grid-auto-columns プロパティで、暗黙的なグリッドに作成されたトラックのセットサイズを定義することもできます

ということです。明示的、暗黙的と表現されていますが、動的なコンテンツに対するサイズ、今回はrowsなので縦のサイズを定義するという意味と捉えています。

補足程度ですが、`grid-template-rows`でもいいです。若干違いはありますが、だいたいおんなじ見た目になります。もしかしたらtemplateの方が良いのかもしれません。

#### .item

次にitemクラスですが、こちらは

```css
.item {
  min-width: 80px;
  margin-left: 8px;
  margin-top: 8px;
  box-sizing: border-box;
  
  background: green;
  color: white;
}
```

と、大したことはやってないので説明は割愛します。

ちなみに、`box-sizing: border-box;`を指定しておかないと予想通りの表示にならなかったりします。多分。

### JavaScript

以下にJavaScriptの全体のコードを載せますが、必要なのは`addItem`だけです。

```javascript
let index = 1;
const addItem = () => {
  const height = randomNum();
  const grid = document.querySelector('#grid');
  const item = document.createElement('div');
  item.setAttribute('class', 'item');
  item.setAttribute('style', `height: ${height}px;grid-row: span ${Math.ceil(height / 20)};`);
  item.innerHTML = index;
  index += 1;
  grid.appendChild(item);
}

const randomNum = () => {
  const min = 40;
  const max = 200;
  return Math.floor(Math.random() * (max - min + 1) + min)
}

window.addEventListener('load', () => {
  for (let i = 0; i < 10; i++) {
    addItem();
  }
  document.querySelector('#addItemButton').addEventListener('click', () => {
    addItem();
  })
})
```

#### addItem

以下にaddItem部分だけ載せます

```javascript
const addItem = () => {
  const height = randomNum();
  const grid = document.querySelector('#grid');
  const item = document.createElement('div');
  item.setAttribute('class', 'item');
  item.setAttribute('style', `height: ${height}px;grid-row: span ${Math.ceil(height / 20)};`);
  item.innerHTML = index;
  index += 1;
  grid.appendChild(item);
}
```

単純に`#grid`に`div.item`を追加するだけのコードですが、重要なのはここです。

```javascript
item.setAttribute('style', `height: ${height}px;grid-row: span ${Math.ceil(height / 20)};`);
```

`style`要素に高さと`grid-row`を書き込んでいます。heightはいいとして、`grid-row`は何でしょうか。

[MDN](https://developer.mozilla.org/ja/docs/Web/CSS/grid-row)によれば、gridのアイテムの実際の高さを決める項目です。`grid-row`は実際には絶対的に数字を入れる必要がありますが、`span`を入れることで指定した値分の高さを埋めるように自動的に位置が決まります。

例えば`grid-row: 3`とした場合はrowが3から始まりますが、`grid-row: span 3`とした場合はgridの高さが3になるように自動的に入り込みます。ちなみにこの高さの単位は、先程指定した`grid-auto-rows`の値になりますので、`span 3`であれば60pxになるようにGridが埋まります。

つまり、ここを`grid-row: span 高さ / grid-auto-rowsで指定した値`とすれば高さを"いい感じに"指定しつつ、配置もCSSのgridの力を借りて"いい感じに"してくれるようになります。

言葉だけでは理解しづらいかもしれませんが、ChromeのDeveloper ToolsのElementsから確認すると案外わかりやすいです。
<img width="925" alt="Gridの詳細表示" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138959/996f7c35-6de1-772e-1ed2-576f6b9d7b1b.png">
CSSの`grid-auto-rows`で20pxごとにラインを引いていきます。それが紫のラインですね。そして、`grid-row`で高さを決めることで、gridの強さを活かしつつ高さも"いい感じに"表示できるって感じですね。

ただ、残念ながらウィンドウ幅を動かしたときに、Magic-Gridのようにカラムが動くようなアニメーションは付きません。悪しからず。

### 最後に

今回の説明ではMDNを利用しつつ軽く説明していますが、私もGrid初心者みたいな存在なので、解釈違いなどあるかもしれませんし、実はGridをグリグリ動かせばjavascriptさえ使わずに実現できるかもしれません。~~magic-grid使ったほうが良くね~~

急ぎ足で解説しましたので乱文ですが、以上となります。
ツッコミ、"こんな方法あるよ"、"このライブラリ便利だよ"等あったらお願いします。