【CSS】グリッドの使い方から配置アルゴリズムまで理解する

この記事は [Happiness Chain Advent Calendar 2023](https://adventar.org/calendars/9414) 13 日目の記事です。

## はじめに

この記事ではCSSのグリッドの基本的な使い方からグリッドアイテムの自動配置アルゴリズムまでを簡単ですがご紹介します。
グリッドを活用することで複雑なレイアウトをより簡単に実現し、レスポンシブなデザインを構築することが可能になります。

※グリッドに関係のないコードは一部省略しています。

## グリッドとは？

グリッドは 2 次元のレイアウトを再現するのに最適な CSS の機能の 1 つです。

以下 MDN の引用です。

> グリッドとは、水平方向と垂直方向のラインを集めたもので、デザイン要素を並べて表示することができます。 ページ間を移動するときに要素が跳び回ったり幅が変わったりしないようなデザインを作成するのに役立ちます。 これにより、ウェブサイトの一貫性が向上します。

[グリッドとは？](https://developer.mozilla.org/ja/docs/Learn/CSS/CSS_layout/Grids#%E3%82%B0%E3%83%AA%E3%83%83%E3%83%89%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E3%81%A8%E3%81%AF%EF%BC%9F)

## グリッドレイアウトの構造

グリッドレイアウトの構造を図に示します。

<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/ee7e3a08-8aa4-e098-b8a7-af5e736def75.png">

<dl>
	<dt>・グリッドコンテナ</dt>
	<dd>
		グリッドの親要素です。子要素はグリッドアイテムとして扱われます。
	</dd>
</dl>
<dl>
	<dt>・グリッドライン</dt>
	<dd>
		グリッドの行や列を定義する線のことです。図のグリッドライン上にある番号はライン番号になります。ライン番号はマイナスでも表す事ができ、最終ラインを-1 としてカウントしています。これらのラインに沿ってグリッドアイテムが配置されます。
	</dd>
</dl>
<dl>
	<dt>・グリッドトラック</dt>
	<dd>
		グリッドコンテナ内の行または列のことです。
	</dd>
</dl>
<dl>
	<dt>・グリッドアイテム</dt>
	<dd>
		実際に配置されるコンテンツ要素です。これらはグリッドコンテナ内の特定のセルに配置され、グリッドの中で位置を決定します。
	</dd>
</dl>
<dl>
	<dt>・グリッドセル</dt>
	<dd>
		グリッドコンテナ内の単一の交差点です。グリッドラインが交わる場所であり、そこにグリッドアイテムが配置されます。
	</dd>
</dl>
<dl>
	<dt>・溝</dt>
	<dd>
		グリッドトラック間の隙間です。
	</dd>
</dl>



## グリッドの使い方

今回は以下のようなコンテナを用意しました。  
グリッドを使うとレイアウトがどのように変化するか見ていきましょう。
```html
<div class="container">
  <div class="item1">1</div>
  <div class="item2">2</div>
  <div class="item3">3</div>
  <div class="item4">4</div>
  <div class="item5">5</div>
  <div class="item6">6</div>
  <div class="item7">7</div>
  <div class="item8">8</div>
  <div class="item9">9</div>
</div>
```

<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/ec214415-4b8c-cb26-5529-433e0d551ff2.png">

### グリッドコンテナを作成する

グリッドコンテナを作成するには親要素に`display: grid;`を設定します。

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

`display: grid;`を設定しただけでは見た目には変化はありません。  
[Chrome のデベロッパーツール](https://developers.google.com/codelabs/devtools-debug-css-grid?hl=ja#0)で確認するとグリッドが設定されている事がわかります。

<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/317840ab-7ecc-5371-b7ba-7c4bbe56e1d2.png">

### 列を作成する

列の作成を行う前にグリッドで新たに追加された`fr`という単位について説明します。  
**fr(fraction)** はグリッドコンテナ内のスペースを計算し、比率に応じてグリッドアイテムを伸縮してくれる便利な単位です。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/a9dfa17e-9e77-e657-4989-689a3362a5f9.png">

本題に戻ります。

`grid-template-columns`プロパティを使用する事で、指定する値の数に応じて列が作成されます。 
次のコードでは利用可能なスペース幅を 3 等分し、グリッドアイテムを配置します。

```css
.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/48b095e5-8b8f-fd23-a31f-5792580be85b.png">

ここでブラウザのウィンドウ幅を可変させてみると、fr のおかげでグリッドアイテムのサイズも追従します。

上のコードは repeat 関数を使う事で、簡潔に記述する事ができます。  
第一引数に繰り返す回数、第二引数に幅を指定します。

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

### 行を作成する

[列を作成する](#列を作成する)では行に関する設定をしていないにも関わらず、結果としては 3 行のグリッドレイアウトが表示されました。これはグリッドアイテムの数が列数を超える場合に、グリッドが自動的に新たな行を作成した為です。

自動で作成された行はデフォルトでグリッドアイテムのコンテンツ(今回の場合は 1,2,3・・・9の数字)に応じて幅が決定されます。

`grid-template-rows`で明示的に行数と幅を指定する事もできます。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 50px 50px;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/9147a25b-e11e-7afd-943a-d1177a7e7ec2.png">

1~2 行目が指定した 50px に設定されました。  
3 行目の幅は指定していないので、コンテンツに応じた幅が設定されていますね。

### グリッドアイテムを配置する

特に指定がない場合に各グリッドアイテムはグリッドセルに自動配置されます。  
位置を指定して配置する場合は次のようにします。

```css
.item1 {
  grid-column-start: 2;
  grid-column-end: 3;
  grid-row-start: 2;
  grid-row-end: 3;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/06740bfe-1654-4b96-6b56-dea3b7e3638e.png">

各プロパティ値はライン番号を指しており、それぞれのラインが交差するセルにグリッドアイテムを配置します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/f9ed6714-6393-82ac-2169-f2c0d3f129a4.png">

`grid-column`、`grid-row`プロパティで`start / end`を同時に指定する事ができます。

```css
.item1 {
  /* start / end */
  grid-column: 2 / 3;
  grid-row: 2 / 3;
}
```

start と end の値が連番であれば end を省略できます。

```css
.item1 {
  /* 2 / 3 と同じ */
  grid-column: 2;
  grid-row: 2;
}
```

複数セルに跨って配置する事もできます。

```css
.item1 {
  grid-column: 2 / 4;
  grid-row: 2 / 4;
}
```

<img width=400 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/52cae868-ec51-3723-86e0-896b4641d6f8.png">

### グリッドアイテムの配置方向を変更する

ここまでのグリッドアイテムは左から右に、上から下へ自動配置されていました。  
この自動配置の方向は`grid-auto-flow`で変更する事ができます。

```css
.container {
  display: grid;

  /* 左から右 上から下 */
  grid-auto-flow: row;

  /* 上から下 左から右 */
  grid-auto-flow: column;

  /* 右から左 上から下 */
  grid-auto-flow: row;
  direction: rtl;

  /* 上から下 右から左 */
  grid-auto-flow: row;
  direction: rtl;
}
```

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/55cf81d6-e59d-8369-895c-5901961f103e.png">

これ以外にも`grid-auto-flow: dense;`がありますが、後程説明します。

### gap

`gap`プロパティを使用することで、グリッドトラック間の余白を調整します。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 50px;
  gap: 5px;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/3e4e1aeb-5e93-5497-4f56-25da85d3e524.png">

行と列で異なる余白を持たせたい場合は引数を 2 つ指定します。

```css
 {
  /* 行間と列間に5px */
  gap: 5px;

  /* 行間に5px 列間に10px */
  gap: 5px 10px;
}
```

## グリッドトラックについて理解する

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

明示的なグリッドトラックとは`grid-template-columns`や`grid-template-rows`プロパティで明確に定義されたグリッドトラックの事を指します。[行を作成する](#行を作成する)で定義したグリッドレイアウトは次のようなものでした。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 50px 50px;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/9147a25b-e11e-7afd-943a-d1177a7e7ec2.png">

これを例に1~2 行目は`grid-template`で明確に定義されており明示的なグリッドトラックと言えます。

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

暗黙的なグリッドトラックは3行目のように明確にサイズが定義されておらず、グリッドによって自動で追加された行や列を指します。

### 暗黙的なグリッドトラックの幅を指定する

`grid-auto-columns`、`grid-auto-rows`で暗黙的なグリッドトラックの幅を指定する事ができます。  
これにより自動で追加されるトラックの幅を制御する事ができます。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 50px;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/064370d0-6d64-5273-2b2a-d235b4e21a5a.png">

複数指定することで 50px → 30px → 50px → 30px・・・と繰り返します。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 50px 30px;
}
```

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/7559e620-90cf-8faf-e891-e365597ea23c.png">

## 自動配置のアルゴリズムを理解する

以下のケースでは、1 番と 2 番のアイテム間に隙間が出来てしまいます。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 50px;
}

.item2 {
  grid-column: 3;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/df5fa642-0072-c343-badf-9df0cd6f592c.png">

2 番のアイテムは列が指定されているので、3 番のアイテムで隙間を埋めてほしいところです。
この問題はたった1つのプロパティで解決できますが、デフォルトでなぜこのようなレイアウトになるのか？グリッドの自動配置アルゴリズムを理解する必要があります。

### 1.匿名グリッドアイテムの作成

自動配置アルゴリムで一番最初に行われるのは匿名グリッドアイテムの作成です。  
匿名グリッドアイテムとはコンテナ内でタグに囲まれていない文字列の事です。  
グリッドはこれらもグリッドアイテムとして取り扱います。

```html
<div class="container">
  <div class="item1">1</div>
  <div class="item2">2</div>
  3
  <div class="item3">4</div>
  <div class="item4">5</div>
  <div class="item5">6</div>
</div>
```

```css
.container {
  display: grid;
  grid-template-columns: repeat(1, 3fr);
  grid-auto-rows: 50px;
}
```

匿名グリッドアイテムがコンテナ内に配置されている事が分かります。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/40b05fc2-9f18-284a-41aa-c1c38a730846.png">

### 2.行と列の位置を明示的に指定したアイテムの配置

行と列の位置を明示的に指定したアイテムの配置を行います。
ここでは行または列のどちらかが指定されていないアイテムは対象外となります。

```css
.item1 {
  grid-column: 1;
}
.item2 {
  grid-column: 1;
  grid-row: 1;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/b627a3aa-7d12-7c0d-1974-a8427ac341fa.png">

1 番と 2 番のアイテムはどちらも列位置を指定してしますが、行位置を指定している 2 番のアイテムの方が優先して配置されている事が分かります。

### 3.行の位置を明示的に指定したアイテムの配置

次に行の位置を明示的に指定したアイテムが配置されます。

```css
.item1 {
  grid-column: 2;
}
.item2 {
  grid-row: 1;
}
.item3 {
  grid-column: 1;
  grid-row: 1;
}
```

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/aaa4421e-8eef-2ab9-f2c6-e67630ae491b.png">

1 番最初に配置されるのは行と列が明示的に指定されている 3 番のアイテムになります。
次に行位置を明示的に指定した 2 番のアイテムが配置されています。

仮に行より列指定の優先順位が高い場合、1 番のアイテムは 2 番のアイテムの位置に来るはずです。  
この事から優先順位は 行&列＞行＞列である事が分かります。

### 4.暗黙的なグリッドトラックの作成

この段階でグリッドは今までに配置されたグリッドアイテムと、列のみ明示的に指定されたグリッドアイテムから必要な列数を計算し、暗黙的なグリッドトラックが必要であれば追加します。

又、まだ配置されていないグリッドアイテムの最大列が明示的なグリッドの幅よりも大きい場合、グリッドの末尾に列を追加してその列スパンに収容します。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
}
.item4 {
  grid-column: 1 / 5;
}
```

デベロッパーツールで確認すると列が1列追加されている事が分かります。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/98ac0c8c-be5b-ac34-1e6f-e02ae8ba28d0.png">

### 5.配置されていないアイテムの配置

最後にまだ配置されていないアイテム(列のみ明示的に指定したアイテムを含む)を配置します。

```css
.item1 {
  grid-column: 2;
}
.item2 {
  grid-row: 1;
}
.item3 {
  grid-column: 1;
  grid-row: 1;
}
.item5 {
  grid-column: 3;
}
```

上のCSSのitem2,3はこれまでのステップを踏まえると以下のように配置ができると思います。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/e1c4164e-e347-ccef-34ef-7f8dc10f6108.png">

残るは列のみ指定されたアイテムと列も行も指定されていないアイテムです。

ここからは DOM の順番(HTML 文書内の要素が現れる順序)に従って配置が行われます。  
よって item1 → item4 → item5 → item6 の順番となります。

配置の説明に入る前に **自動配置カーソル** という仕組みを理解する必要があります。
自動配置カーソルはこれから配置していくアイテムの挿入位置を表し、行番号と列番号によって管理されています。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/f4bca74d-1c77-8cb1-a632-fd0dcaa6ccae.png">

自動配置カーソルの移動は`grid-auto-flow`プロパティに基づいて行われます。
今回は`grid-auto-flow: row`である事を前提に話を進めていきます。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/90f26c57-973c-bd41-1d97-65e8b60509df.png">

- 1 番のアイテム (grid-column: 2)
  - 自動配置カーソルは指定された列位置で行を 1 行ずつずらしながら、空のセルを探します。1 行目 2 列目は 2 番のアイテムが既に配置されている為、１行下に行って、2 行目 2 列目のセルを確認します。空のグリッドセルなので 1 番のグリッドアイテムを配置します。
- 4 番のアイテム
  - 自動配置カーソルは**前回の位置を引き継ぐ**ため 2 行目 3 列目からスタートします。早速 空のグリッドセルが見つかったので 4 番のグリッドアイテムを配置します。
- 5 番のアイテム (grid-column: 3)
  - 前回の位置の続きからであれば 3 行目 1 列目からスタートですが、列位置が指定されているので 3 行目 3 列目からスタートします。空のグリッドセルなので 5 番のアイテムを配置します。
- 6 番のアイテム
  - 4 行目 1 列目からスタートし、空のグリッドセルなので 6 番のアイテムを配置します。

### グリッドセルの隙間を埋めるには

上で解説したアルゴリズムは`grid-auto-flow: sparse`の場合の挙動で、デフォルトの動作になります。`grid-auto-flow: dense`を指定する事で『[配置されていないアイテムの配置](配置されていないアイテムの配置)』のアルゴリズムが変わります。

`sparse`では自動配置カーソルの位置は前回の配置位置を引き継いでいましたが、`dense`では自動配置カーソルが毎回初期値(今回の場合 1 行目 1 列目)から開始されます。

これによってグリッドアイテムが隙間なく配置されます。

```css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 50px;
  grid-auto-flow: dense;
}
.item1 {
  grid-column: 2;
}
.item2 {
  grid-row: 1;
}
.item3 {
  grid-column: 1;
  grid-row: 1;
}
.item5 {
  grid-column: 3;
}
```

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3551297/c8cb23be-ea35-fb78-154c-593afd5f3157.png">

## おわりに

今回はグリッドの使い方と自動配置アルゴリズムについて触れていきました。  
自分もまだ知らないグリッドの使い方がたくさんあり、紹介した内容はほんの一部に過ぎないと思います。  
グリッドの使い方をもっと学びたい！という方は [MDN](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout) を参照下さい。

## 参考文献

[CSS Grid Layout Module Level 2](https://www.w3.org/TR/css-grid-2/)

[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/)

[CSS グリッドレイアウトでの自動配置](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout/Auto-placement_in_grid_layout)

[グリッドレイアウトの基本概念](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout/Basic_concepts_of_grid_layout)

[グリッドレイアウトの配置アルゴリズムを理解する](https://qiita.com/otsuky/items/7a81b59bee4b352417ce)