LoginSignup
5

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

Last updated at Posted at 2023-01-30

はじめに

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

前提知識

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

CSSのレイアウトモードをグリッドレイアウトにするには、要素の display プロパティに grid もしくは inline-grid を指定します。

<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
.container {
  display: grid;
}

.containerの要素はグリッドコンテナとなり、その子要素である.itemグリッドアイテムとして扱われ、グリッド上に並べることができます。
グリッドコンテナとグリッドアイテム.png

グリッドライン

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

グリッドライン.png

グリッドトラック

グリッドトラックは、任意の2本のグリッドライン線で挟まれた空間(行/列)を指します。
グリッドコンテナ (3).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を使用してグリッドトラックを生成

<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
.container {
  display: grid;
  grid-template-rows: repeat(3, 80px);
}

See the Pen Untitled by otky (@otky) on CodePen.

grid-template-rows - CSS: カスケーディングスタイルシート | MDN

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

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

See the Pen Untitled by otky (@otky) on CodePen.

grid-template-columns - CSS: カスケーディングスタイルシート | MDN

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

<div class="container">
  <div class="item-a">A</div>
  <div class="item-b">B</div>
  <div class="item-c">C</div>
</div>
.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;
}

See the Pen Untitled by otky (@otky) on CodePen.

grid-template-areas - CSS: カスケーディングスタイルシート | MDN

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

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

<div class="container">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
</div>
.container {
  /* 明示的なグリッドトラックなし */
  display: grid;
}

See the Pen Untitled by otky (@otky) on CodePen.

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

implicit-grid-tracks.png

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

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

自動配置の方向

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

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

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

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

無名グリッドアイテム

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

<div class="container">
  <div class="item">A</div>
  B <!-- 無名グリッドアイテム -->
  <div class="item">C</div>
</div>
.container {
  display: grid;
}

配置のステップ

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

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

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

order - CSS: カスケーディングスタイルシート | MDN

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

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

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

<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>

.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-areagrid-row-start/ grid-column-start / grid-row-end/ grid-column-end 一括指定プロパティで、指定する値によってグリッドアイテムの位置とサイズを決定できます。

grid-area - CSS: カスケーディングスタイルシート | MDN

See the Pen Untitled by otky (@otky) on CodePen.

grid-area - CSS: カスケーディングスタイルシート | MDN

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

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

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

sparseの場合

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

<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>
.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;
}

See the Pen sparse by otky (@otky) on CodePen.

denseの場合

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

<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>
.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;
}

See the Pen Untitled by otky (@otky) on CodePen.

3. 暗黙的なグリッドトラック(列)の数を決定

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

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

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

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

sparseの場合

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

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

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

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

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

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

<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>
.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;
}

See the Pen Remaining Sparse items by otky (@otky) on CodePen.

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

自動配置カーソルの初期位置は行/列共に1番目の位置です。
スクリーンショット 2023-01-19 9.32.15.png

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

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

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

denseの場合

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

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

<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>
.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;
}

See the Pen remaining dense items by otky (@otky) on CodePen.

grid-auto-flow: dense;を指定したことで行のみ明示的な位置が定義されているDの位置が移動しています。( 2. 明示的に行の位置が指定されているグリッドアイテムを配置 を参照)

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

おわりに

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

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5