グリッドレイアウトの配置アルゴリズムを理解する

# はじめに

[CSS グリッドレイアウト](https://drafts.csswg.org/css-grid/)はHTML/CSSを使って2次元レイアウトを柔軟に実現できるCSSの仕様です。
2022年6月にIE11のサポートが終了したことはまだ記憶に新しいですが、IEのサポート終了によってグリッドレイアウトを本格的に活用できるようになった方も多いのではないでしょうか。
グリッドレイアウトはこれまでの実装方法によりも簡単に要素の配置を操作できたり、複雑な表現を可能にしたりする非常に強力な仕様です。
一方で、機能や関連プロパティが多いためなんとなくいい感じにレイアウトできることもあれば、逆になぜかわからないけど思ったようにならなかったり…と詳細を理解して自由自在に扱うことは容易くありません。
そこでこの記事では、グリッドレイアウトをマスターして使いこなすために知っておきたい配置アルゴリズムを解説します。

# 前提知識

## グリッドレイアウトを使う

CSSの[レイアウトモード](https://developer.mozilla.org/ja/docs/Web/CSS/Layout_mode)をグリッドレイアウトにするには、要素の `display` プロパティに `grid`  もしくは `inline-grid` を指定します。

```html
<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
```

```css
.container {
  display: grid;
}
```

`.container`の要素は**グリッドコンテナ**となり、その子要素である`.item`は**グリッドアイテム**として扱われ、グリッド上に並べることができます。
![グリッドコンテナとグリッドアイテム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174446/5364bd48-1f99-fa57-1b2e-c887c46efea6.png)

## グリッドライン

**グリッドライン**とは、グリッドの水平方向と垂直方向の区切り線のことです。

![グリッドライン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174446/eb7c0894-cad1-f368-bf6a-8820358b3748.png)


## グリッドトラック

**グリッドトラック**は、任意の2本のグリッドライン線で挟まれた空間（行/列）を指します。
![グリッドコンテナ (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174446/930ed58b-8510-046e-7184-712b8f49ffdf.png)
グリッドトラックを生成するとグリッドアイテムがグリッドトラック上に配置されます。
グリッドトラックには**明示的なグリッドトラック（explicit grid tracks）** と**暗黙的なグリッドトラック（implicit grid tracks）** があります。

### 明示的なグリッドトラック

下記の3つのプロパティで作成されたグリッドトラックは明示的なグリッドトラックと呼ばれます。

- `grid-template-rows`
- `grid-template-columns`
- `grid-template-areas`

明示的なグリッドトラックのサイズは`grid-template-rows`/`grid-template-columns`の値によって決定します。
`grid-template-areas`で生成し、`grid-template-rows`/`grid-template-columns`を指定しない場合は`grid-auto-rows`/`grid-auto-columns`の値によって決まります。

**`grid-template-rows`を使用してグリッドトラックを生成**

```html
<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
```

```css
.container {
  display: grid;
  grid-template-rows: repeat(3, 80px);
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="YzjXNmX" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/YzjXNmX">
  Untitled</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

[grid-template-rows - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/grid-template-rows)

**`grid-template-columns`を使用してグリッドトラックを生成**

```html
<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
```
```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="VwBLPoY" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/VwBLPoY">
  Untitled</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

[grid-template-columns - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/grid-template-columns)

**`grid-template-areas`を使用してグリッドトラックを生成**

```html
<div class="container">
  <div class="item-a">A</div>
  <div class="item-b">B</div>
  <div class="item-c">C</div>
</div>
```

```css
.container {
  display: grid;
  grid-template-areas: "a a"
                       "b c";
}

.item-a {
  grid-area: a;
}

.item-b {
  grid-area: b;
}

.item-b {
  grid-area: c;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="rNJKYNE" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/rNJKYNE">
  Untitled</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

[grid-template-areas - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/grid-template-areas)

### 暗黙的なグリッドトラック

明示的なグリッドトラックがない場合や、作成したグリッドトラックの数よりグリッドアイテムの数が多い場合にはトラックが自動的に拡張されます。
これは暗黙的なグリッドトラックと呼ばれます。
暗黙的なグリッドトラックのサイズは`grid-auto-rows`/`grid-auto-columns`の値によって決まります。

```html
<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
```

```css
.container {
  /* 明示的なグリッドトラックなし */
  display: grid;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="eYVKeYa" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/eYVKeYa">
  Untitled</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>



一見わかりづらいですが、デベロッパーツールから明示的なグリッドトラックの指定がなくてもグリッドトラックが生成されていることが確認できます。

![implicit-grid-tracks.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174446/45d13df3-bd0f-361d-a861-a01bcd945bae.png)


# グリッドレイアウトの配置

いよいよ本題です。
グリッドアイテムは、プロパティを指定することにより明示的に配置することも自動で配置することも可能です。
明示的に位置を指定しない場合、グリッドアイテムはグリッド上の各セルに1つずつ配置されます。


## 自動配置の方向

`grid-auto-flow` プロパティを使用することで自動配置の方向を指定できます。

- `grid-auto-flow: row;`
左から右 → 上から下の順にグリッドを埋めていき、暗黙的なグリッドトラックは新しい行として下に生成されます

- `grid-auto-flow: column;`
上から下 → 左から右の順にグリッドを埋めていき、暗黙的なグリッドトラックは新しい列として右に生成されます

`grid-auto-flow`の初期値は`row`なので、`grid-auto-flow`を指定しない場合は左から右、上から下の順に配置されます。

## 無名グリッドアイテム

無名（Anonymous）グリッドアイテムは、タグで囲まず直接グリッドコンテナに配置されたテキストがある場合に生成されます。
スタイルを指定できないため**常に自動配置**されます。
自動配置されることを把握していない場合、思わぬ場所に表示されることがあるので注意が必要です。

```html
<div class="container">
  <div class="item">A</div>
  B <!-- 無名グリッドアイテム -->
  <div class="item">C</div>
</div>
```

```css
.container {
  display: grid;
}
```


## 配置のステップ

グリッドレイアウトでは次のステップで配置が行われます。


1. 自動配置されないグリッドアイテムを配置
1. 明示的に行の位置が指定されているグリッドアイテムを配置
1. 暗黙的なグリッドトラック（列）の数を決定
1. 残りのグリッドアイテムを配置

通常の場合、グリッドアイテムはDOMの順番で処理されます。
ただし、`order` プロパティによってグリッドアイテムの並び順が指定されている場合はその順番にしたがって処理されます。

[order - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/order)

以下では`grid-auto-flow: row`であると仮定します。
`column`を指定したパターンが知りたい場合は「列（column）」→「行（row）」のように読み替えてください。

### 1. 自動配置されないグリッドアイテムを配置

まず、明示的な位置の指定があり、自動配置されないグリッドアイテムを配置します。
下記の例では、「B」のグリッドアイテムのみ明示的な位置の指定があります。
このステップでは他のグリッドアイテムは無視されます。

```html
<div class="container">
  <div class="item">A</div>
  <div class="item explicitly-layout">B</div>
  <div class="item">C</div>
  <div class="item">D</div>
</div>

```

```css
.container {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
}

.explicitly-layout {
  /* 以下と同義
     grid-row-start: 2;
     grid-column-start: 1;
     grid-row-end: 3;
     grid-column-end 3; */
  grid-area: 2 / 1 / 3 / 3;
}
```

`grid-area`は `grid-row-start`/ `grid-column-start` / `grid-row-end`/ `grid-column-end` 一括指定プロパティで、指定する値によってグリッドアイテムの位置とサイズを決定できます。

[grid-area - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/grid-area)

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="eYRXBjZ" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/eYRXBjZ">
  Untitled</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


[grid-area - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/grid-area)


### 2. 明示的に行の位置が指定されているグリッドアイテムを配置

`grid-row-start` と `grid-row-end` プロパティの値によって明示的に行の位置が確定しているグリッドアイテムを配置します。
このアイテムは列の位置は明示的に指定されていないため、自動で処理されます。
この列の自動配置のアルゴリズムには`sparse`と`dense`の2種類のパッキングアルゴリズムがあります。

これらは`grid-auto-flow`で指定することができ、初期値は`sparse`です。

#### `sparse`の場合

グリッドアイテムの列の開始位置は、**このステップで同じ行に既に配置したほかのグリッドアイテムより後の開始位置で**、ほかのグリッドアイテムと重なり合わない最小の開始位置に設定されます。

```html
<div class="container">
  <div class="item item-a">A</div>
  <div class="item item-b">B</div>
  <div class="item item-c">C</div>
  <div class="item item-d">D</div>
</div>
```

```css
.container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.item-a {
  grid-area: 1 / 2 / 2 / 3;
}

.item-b {
  grid-area: 2 / 1 / 2 / 3;
}

.item-c {
  grid-row: 1 / 3;
}

.item-d {
  grid-row: 1 / 2;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="eYjNRJE" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/eYjNRJE">
  sparse</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

#### `dense`の場合

グリッドアイテムの列の開始位置は、既に配置したほかのグリッドアイテムと重なり合わない最小の開始位置に設定されます。
つまり、これまでに配置されたグリッドアイテムに隙間があり、これから配置するグリッドアイテムがそこにおさまるのならDOM（や`order`）の順番を無視してそこに配置されます。

```html
<div class="container">
  <div class="item item-a">A</div>
  <div class="item item-b">B</div>
  <div class="item item-c">C</div>
  <div class="item item-d">D</div>
</div>
```

```css
.container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* ↓ この行を追加 */
  grid-auto-flow: dense;
}

.item-a {
  grid-area: 1 / 2 / 2 / 3;
}

.item-b {
  grid-area: 2 / 1 / 2 / 3;
}

.item-c {
  grid-row: 1 / 3;
}

.item-d {
  grid-row: 1 / 2;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="NMvpvg" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/NMvpvg">
  Untitled</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


### 3. 暗黙的なグリッドトラック（列）の数を決定

暗黙的なグリッドトラック（列）の数を決定します。
まず、列の位置が確定しているすべてのグリッドアイテム（明示的に配置されたグリッドアイテムや前のステップで配置されたグリッドアイテム、まだ配置されていないが列が確定しているグリッドアイテム）を配置するために必要な列を暗黙的なグリッドトラックとして追加します。
次に、列の位置が確定していないすべてのグリッドアイテムのうち、最大の列幅が暗黙のグリッドの幅より大きい場合はその列幅に対応するように暗黙のグリッドトラックを末尾に追加します。

### 4. 残りのグリッドアイテムを配置

最後に残りのグリッドアイテムを配置します。
ここで **自動配置カーソル（auto-placement cursor）** という概念が登場します。
自動配置カーソルはグリッドにおける現在の挿入位置を表し、グリッドラインの行と列のインデックスを指定します。
初期状態は最初の暗黙的なグリッドトラックの初めの行と列が設定されています。

このステップでも`grid-auto-flow`の値のパッキングアルゴリズムにしたがって配置されます。

#### `sparse`の場合

デフォルトでは`sparse`として処理されます。


##### グリッドアイテムが明示的な列の位置が定義されてるとき

1.自動カーソルの列をグリッドアイテムが開始されるグリッドラインに設定します。
この値が自動カーソルの前の列位置より小さい場合は、行を1つ増やします。
2.他のグリッドアイテムと重ならない位置が見つかるまで行を1つずつ増やしていきます。
この時、必要に応じて暗黙的なグリッドトラック（行）を作成します。
3.他のグリッドアイテムと重ならない位置が見つかったら、グリッドアイテムの行の開始位置は自動カーソルの行の位置に設定されます。


##### グリッドアイテムの位置が定義されていなとき

1. 既に配置されているグリッドアイテムと重ならない値が見つかるまで自動配置カーソルの列の値を1つずつ増やします。
現在の行では重ならない値見つからず、カーソルの列位置とグリッドアイテムの列幅が前の手順で決定した暗黙的なグリッドトラックの列数をオーバーフローする場合は自動配置カーソルの行を1つを増やします。
この時、必要に応じて暗黙的なグリッドトラック（行）を作成します。
自動配置カーソルの列を暗黙のグリッドの最初の列に設定し、この手順を繰り返します。
2. グリッドアイテムと重ならない値が見つかったら、このグリッドアイテムの行の開始位置と列の開始位置を自動配置カーソルの位置に設定します。

次のグリッドアイテムを配置する際の自動カーソルの位置は、直前のグリッドアイテムの位置から始めます。

下記の例で実際に見てみましょう。

```html
<div class="container">
  <div class="item item-a">A</div>
  <div class="item item-b">B</div>
  <div class="item item-c">C</div>
  <div class="item item-d">D</div>
  <div class="item item-e">E</div>
  <div class="item item-f">F</div>
</div>
```

```css
.container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.item-a {
  grid-area: 1 / 2 / 2 / 3;
}

.item-b {
  grid-area: 2 / 1 / 2 / 3;
}

.item-c {
  grid-row: 1 / 3;
}

.item-d {
	grid-row: 1 / 2;
}

.item-e {
	grid-column: 2 / 4;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="ZEjaema" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/ZEjaema">
  Remaining Sparse items</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

- E → 明示的な列の位置があるグリッドアイテム
- F → 位置が定義されていないグリッドアイテム


自動配置カーソルの初期位置は行/列共に1番目の位置です。
![スクリーンショット 2023-01-19 9.32.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174446/697166a7-d540-d4ff-79ca-c4b14ebadb75.png)

E には明示的な列位置の指定があります。

```css
.item-e {
	grid-column: 2 / 4;
}
```

1行目、2行目のグリッドトラックでこの`grid-column`を満たす位置には既にアイテムがあって配置することができないため、3行目に配置されます。
ここで自動配置カーソルの位置はEの`row-start`と`column-start`の値、つまり3行目/2列目に設定されます。
Fは明示的な位置が定義されていないため、この自動配置カーソルの位置から既存のアイテムに重ならない位置を探して配置されます。

#### `dense`の場合

`sparse`と違いは、グリッドアイテムを配置配置する際に毎回自動カーソルの初期位置が暗黙的なグリッドトラックの開始位置になることです。

先ほどの`sparse`の例のコードに`grid-auto-flow: dense`を指定して見てみましょう。

```html
<div class="container">
  <div class="item item-a">A</div>
  <div class="item item-b">B</div>
  <div class="item item-c">C</div>
  <div class="item item-d">D</div>
  <div class="item item-e">E</div>
  <div class="item item-f">F</div>
</div>
```

```scss
.container {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  // ↓ この行を追加
  grid-auto-flow: dense;
}

.item-a {
  grid-area: 1 / 2 / 2 / 3;
}

.item-b {
  grid-area: 2 / 1 / 2 / 3;
}

.item-c {
  grid-row: 1 / 3;
}

.item-d {
	grid-row: 1 / 2;
}

.item-e {
	grid-column: 2 / 4;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="xxJPdzK" data-user="otky" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/otky/pen/xxJPdzK">
  remaining dense items</a> by otky (<a href="https://codepen.io/otky">@otky</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

`grid-auto-flow: dense;`を指定したことで行のみ明示的な位置が定義されているDの位置が移動しています。（ [2. 明示的に行の位置が指定されているグリッドアイテムを配置](https://qiita.com/otsuky/private/7a81b59bee4b352417ce#2-%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AB%E8%A1%8C%E3%81%AE%E4%BD%8D%E7%BD%AE%E3%81%8C%E6%8C%87%E5%AE%9A%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%82%B0%E3%83%AA%E3%83%83%E3%83%89%E3%82%A2%E3%82%A4%E3%83%86%E3%83%A0%E3%82%92%E9%85%8D%E7%BD%AE) を参照）

自動配置カーソルの初期位置は行/列共に1の位置です。
Eは`sparse`の場合と同様に配置されますが、自動配置カーソルの位置は初期の行/列共に1の位置に戻ります。
そして明示的な位置が定義されていないFは既に配置されているアイテムと重ならない位置を探して配置されます。

# おわりに

この記事ではグリッドレイアウトの自動配置アルゴリズムについてざっと見ていきました。
グリッドすごいですね！メリークリスマス！


# 参考
- [CSS グリッドレイアウト Module Level 2](https://drafts.csswg.org/css-grid/)
- [CSS グリッドレイアウトでの自動配置 - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_Layout/Auto-placement_in_CSS_grid_Layout)
- [A Guide to the Auto-Placement Algorithm in CSS Grid](https://www.sitepoint.com/a-step-by-step-guide-to-the-auto-placement-algorithm-in-css-grid/)
- [ASCII.jp：徹底検証！CSS グリッドレイアウト moduleの自動配置アルゴリズムはどう働くか？](https://ascii.jp/elem/000/001/451/1451768/)
- [HTML5&CSS3デザイン 現場の新標準ガイド【第2版】](https://book.mynavi.jp/ec/products/detail/id=117364)