この記事では、要素を配置する際にCSS Grid Layout Moduleの自動配置アルゴリズムがたどるすべてのステップを説明します。ステップはgrid-auto-flowプロパティでコントロールされています。
『Flexboxよりも新しい!CSSレイアウトの最新仕様Grid Layout を先取りしよう』や『2017年に学びたい!次世代CSSレイアウト「Grid Layout Module」の使い方』で、CSS Grid仕様の概要とGridでWebに要素を配置できる多様な方法すべてを説明しました。とはいえ先の記事ではグリッド内の単一の要素の位置を明示的に指定しただけでした。ほかの複数のアイテムはアルゴリズムに基づいて適切に配置されます。
この記事ではアルゴリズムがどのように動作するか説明します。今後、要素が予想外の位置に配置されてしまっても、起きたことが理解できなくて頭を悩ませ続けることはなくなるでしょう。
自動配置アルゴリズムをよりよく理解するための基本概念
アルゴリズムの動作説明に入る前に基本概念を説明します。
- 匿名の(Anonymous)グリッドアイテム:タグで囲まずにテキストを直接グリッドコンテナ内に配置すると、テキスト自体が匿名のグリッドアイテムになる。スタイルを適用できる要素が存在しないので匿名のグリッドアイテムはスタイリングできないが、親コンテナのスタイルルールは継承されている。一方グリッドコンテナ内のホワイトスペースは匿名のグリッドアイテムにならないことに注意
- グリッドのスパン値:グリッド位置とは異なり、グリッドのスパン値を解決する特定のルールはアルゴリズムに含まれない。明示的に指定されないとスパン値は1(アイテムがそのセルのみを占める)に設定される
- 暗黙的なグリッド:grid-template-rows、grid-template-columns、grid-template-areasなどのプロパティ値に基づいて作成されるグリッドは明示的なグリッドと呼ばれる。グリッドアイテムの位置を明示的なグリッドの境界外に指定する場合、ブラウザーはアイテムを収めるために追加のグリッド線を生成する。グリッド線は明示的なグリッドとともに暗黙的なグリッドを形成する。詳しくは『CSSレイアウトの常識が変わる!Grid Layout Moduleの最新動向を追いかけろ』を参照。結果的に自動配置アルゴリズムによって暗黙的なグリッドに追加の行や列を作成できる
最後に前置きとして次の点を説明します。アルゴリズムをコントロールするgrid-auto-flowプロパティのデフォルト値はrowです。このあとの自動配置アルゴリズムの説明でもこの値(row)を前提とします。逆にこのプロパティの値を明示的にcolumnに設定する場合、アルゴリズムの説明中の用語が「行(row)」のところは「列(column)」に置き換えてください。たとえば「列位置ではなく行位置で設定された要素の配置」というステップは「行位置ではなく列位置で設定された要素の配置」となります。
ではお気に入りのモダンブラウザーで試験運用版機能フラグを有効にして、アルゴリズムがレイアウトを構築するすべてのステップを一緒に調べていきます。
ステップ1:匿名のグリッドアイテムの生成
すべてのアイテムをグリッド内に配置するアルゴリズムの最初のステップは匿名グリッドアイテムの作成です。先に述べたとおり、スタイルを適用できるアイテムが存在しないので、要素のスタイリングはできません。
下のマークアップで要素内のテキストから匿名のグリッドアイテムが生成されます。
<div class="container">
<span class="nonan">1</span>
Anonymous Item
<div class="nonan floating">2</div>
<div class="nonan">3</div>
<div class="nonan floating">4</div>
<div class="nonan">5</div>
</div>
匿名のアイテムの生成とは別にもう1つの注目すべき点として、下のデモではグリッド配置アルゴリズムがdiv 2とdiv 4に適用されたCSSのfloatを無視しています。
ステップ2:明示的に位置を指定された要素を配置
このステップと次のステップでは、9個の異なるアイテムのグリッドを使ってアイテムがどのように配置されていくかを説明します。
関連するマークアップは次のとおりです。
<div class="container">
<div class="item a">A</div>
<div class="item b">B</div>
<div class="item c">C</div>
<div class="item d">D</div>
<div class="item e">E</div>
<div class="item f">F</div>
<div class="item f">G</div>
<div class="item f">H</div>
<div class="item f">I</div>
</div>
明示的に位置が設定されたアイテムが最初にグリッドに配置され、下の例ではアイテムA、Bがこれに当たります。ここではグリッド内のほかのアイテムはすべて無視します。AとBの位置を明示的に設定するCSSは次のとおりです。
.a {
grid-area: 1 / 2 / 2 / 3;
}
.b {
grid-area: 2 / 1 / 4 / 3;
}
アルゴリズムはそれぞれのgrid-areaプロパティ値に従ってアイテムA、Bを配置します。具体的に次のように設定します。
- AとB両方の左上端の位置をgrid-areaプロパティの最初と2番目の値で設定
- AとB両方の右下端の位置をgrid-areaプロパティの3番目と4番目の値で設定
下のデモでこのステップ後のAとBの配置結果を示します。
ステップ3:列位置ではなく行位置で設定された要素を配置
アルゴリズムは次に、grid-row-startプロパティとgrid-row-endプロパティを使って行位置が明示的に設定された要素を配置します。
この例では、アイテムCとアイテムDの行位置を定義するためにgrid-rowの値のみを設定します。CとDを配置するCSSは次のとおりです。
.c {
grid-row-start: 1;
grid-row-end: 3;
}
.d {
grid-row-start: 1;
grid-row-end: 2;
}
明示的に設定されない列位置を決定する際、アルゴリズムは次の2種類のパッキングモードのうちの1つに従って反応します。
- sparse packing(sparseパッキング、デフォルト。日本版編注:sparseは「疎」「まばらな」の意)
- dense packing(denseパッキング。日本版編注:denseは「密」「密集した」の意)
ステップ3のSparseパッキング
デフォルトでの反応はsparseパッキングです。アイテムの列開始ラインは、アイテム自体のグリッド領域と別のアイテムによってすでに占められているセルとが決して重なり合わない条件下で最小のライン番号に設定されます。列開始ラインは、ステップ3において同じ行にすでに配置されたほかのどのアイテムよりもライン番号が大きくなければなりません。ちなみに「ステップ3において」であり「ステップ3までに」ではありません。
この点をさらにはっきり示しているのは、重なり合わずに収められるにもかかわらず、下のデモでアイテムDがアイテムAの左に移動していないことです。このようになるのは、アルゴリズムは行位置が明示的に設定されているものの列位置が設定されていないアイテムを、その特定の行で同様に配置されているほかのアイテム(この場合アイテムC)よりも手前には配置しない、というルールを持っているからです。実際、アイテムCに適用されているgrid-rowのルールを削除すると、アイテムAはアイテムDの左に移動します。
簡単に言うと、アイテムDは行位置が定義されているものの列位置は明示的に設定されていないのでアイテムAよりも手前に配置される可能性がありますが、あくまでCに干渉されない場合に限ります(この場合干渉はCによって起こりましたが、CはDと同様に行位置が定義されていて列位置は設定されておらず、しかもDと同一の行内にあります)。
ステップ3のDenseパッキング
アイテムAの手前の空白をアイテムDで埋めたいと思う場合、grid-auto-flowプロパティの値をrow denseに設定する必要があります。
.container {
grid-auto-flow: row dense;
}
この場合も、列開始ラインはほかのグリッドアイテムと重なり合わない条件で最小の番号に配置されます。このとき唯一違うのは、行内に重なり合わずに要素を収められる空白がある場合、位置ルールが同じで同一の行内にある先行のアイテム(この場合アイテムC)を意識せずにアイテムがその(空白の)位置に配置されることです。
ステップ4:暗黙的なグリッドの列数の決定
次にアルゴリズムは暗黙的なグリッドの列数を決定します。以下の一連のステップに従って実行されます。
- アルゴリズムは明示的なグリッドの列数で開始する
- 次いで列位置が定義されているすべてのグリッドアイテムを経由し、すべてのアイテムを収めるために暗黙的なグリッドの開始部分と終了部分に列を追加する
- 最後に列位置が定義されていないすべてのグリッドアイテムを経由する。アイテム間で最大の列スパンが暗黙的なグリッドの幅よりも大きい場合、列スパンを収めるためにグリッドの終了部分に列を追加する
ステップ5:残りのアイテムの配置
この時点で、アルゴリズムは行位置が認識されているアイテムだけでなく明示的に位置を指定されたアイテムもすべて配置済みです。次に残るすべてのアイテムをグリッド内に配置します。
さらに詳しく説明する前に、「自動配置カーソル(auto-placement cursor)」ついて確認しておきます。自動配置カーソルはグリッドにおける現在の挿入位置を定義し、行と列のグリッド線のペアとして指定されます。開始時に自動配置カーソルは暗黙的なグリッドの行と列の最初の開始位置(start-most)にあります。
再びgrid-auto-flowプロパティの値で定義されたパッキングモードがアイテムの配置をコントロールします。
ステップ5のSparseパッキング
デフォルトで、残りのアイテムはsparseモードで配置されます。以下にアルゴリズムが残りのアイテムを配置するプロセスを示します。
アイテムがどちらの軸でも配置を定義されていない場合は次のとおりです。
- アルゴリズムは、
a)すでに配置済みのアイテムと現在のアイテムが重なり合わない
または、
b)アイテムの列スパンを加えたカーソルの列位置が暗黙的なグリッドの列数を超えるまでカーソルの列位置をインクリメントする - 重なり合わない位置が見つかったら、アルゴリズムはrow-startとcolumn-startの値をカーソルの現在位置に設定する。もしくは行位置を1つインクリメントしてcolumn-startを暗黙的なグリッドの最初の開始ラインに設定し、これまでのステップを繰り返す
アイテムの列位置が定義されている場合は次のとおりです。
- 自動配置カーソルの列位置はアイテムのcolumn-startラインに設定される。この新規位置の値がカーソルの先行の列位置(番号)よりも小さい場合、行位置が1つインクリメントされる
- さらにグリッドアイテムがすでに要素の入っているグリッドセルと重なり合わないような値にアルゴリズムが行きつくまで、行位置は1つずつインクリメントされる。必要な場合暗黙的なグリッドに行が追加されることもある。ここでアイテムの行の開始ラインはカーソルの行位置に設定され、アイテムの行の終了ラインはアイテムのスパンに従って設定される
次のデモで上に挙げたステップを分かりやすく説明します。
■どちらの軸でも位置を定義されていないアイテムEとFの配置
列位置も行位置も明示的に設定されていないアイテムEをアルゴリズムが処理する場合、自動配置カーソルは行1と列1に設定されます。アイテムEはセルを1つだけ占め、重なり合わずに左上隅に位置できます。アルゴリズムはシンプルにアイテムEを行1/列1の位置に配置します。
列位置も行位置も明示的に設定されていない次のアイテムはFです。この時点で自動配置カーソルの列位置は2までインクリメントされます。ところが行1/列2にはすでにアイテムAが入っています。このためアルゴリズムは列をさらにインクリメントしなければならず、インクリメントは自動配置カーソルが列4に行きつくまで続きます。もう列がないので、自動配置カーソルの行位置が1つインクリメントされて列位置は1に設定されます。ここで行位置は2、列位置は1になります。アルゴリズムは再び列位置を1つずつインクリメントして列4まで進みます。行2/列4のスペースは現在空白なのでアイテムFを収められます。アルゴリズムはアイテムFをそこに配置して次のアイテムに移ります。
■列位置を定義されているアイテムGとHの配置
デモで列位置が定義されている残りのアイテムはGとHです。Gについて取り上げます。自動配置カーソルの列位置はアイテムGのgrid-column-startプロパティの値と同じく3に設定されます。この新規の値は先行の列の値(4)より小さいため、行位置が1つインクリメントされます。ここで現在の行と列の位置はどちらも3となります。行3・列3のスペースは現在空いているのでアイテムGを重ならないように配置できます。アルゴリズムはそこにアイテムGを配置します。次いで同じステップを繰り返してアイテムHを配置します。
ステップ5のDenseパッキング
grid-auto-flowプロパティがrow denseに設定されている場合、処理が少し異なります。位置が定義されていないグリッドアイテムを配置する場合、カーソルの現在位置はアイテムの位置が決定される前に暗黙的なグリッドの行と列の最初の開始ラインに設定されます。
先に紹介した例とは異なり、アイテムIはアイテムHの左に配置されます。なぜならカーソル位置は直前に配置されたアイテムの位置から開始するのではなく、暗黙的なグリッドの行と列の最初の開始ラインにリセットされるからです。この時点で重なり合わずにアイテムIを配置する適切な位置を探す中で、カーソルはアイテムHの左にスポットを見つけそこにアイテムIを配置します。
最後に
記事ではgrid-auto-flowプロパティによってコントロールされる、CSS Grid Layout moduleの自動配置アルゴリズムがたどるすべてのステップについて説明しました。
アルゴリズムをより良く理解するために、別のいくつかのレイアウトでいろいろなアイテムの最終的な位置を割り出してみてください。
(原文:A Step by Step Guide to the Auto-Placement Algorithm in CSS Grid)
[翻訳:新岡祐佳子/編集:Livit]