CSSのグリッド・レイアウトを理解する

グリッド・レイアウト(Grid Layout)とは何か

グリッド・レイアウトは、CSSで操作可能なレイアウト・モデルの一種です。グリッドとは、行と列を定義する水平線と垂直線の集合が交差する二次元の空間です。この格子状の仮想範囲を作成し、そこに子要素をはめ込んでいく作業がグリッド・レイアウトの基本的な流れです。

グリッド・レイアウトのモデルでは、通常のフロー・レイアウトやフレックス・ボックスにはない概念が登場します。それらを正確に理解するためには、各プロパティの機能よりも先に言葉の意味を把握しておく方が大事です。

以降の内容を読み進める前に、CSSのボックス・モデルについて理解しておきましょう。ボックス・モデルは、CSSがHTMLの要素をどのように扱うのかを表す基本的な概念です。この仕組みの拡張機能の一種が、グリッド・レイアウトなのです。

グリッド・レイアウトを開始する

グリッド・レイアウトを開始するには、まず初めにグリッド・レイアウトを適用したい要素にdisplayプロパティを追加し、その値にgridまたはinline-gridを指定します。

この状態になった要素のことをグリッド・コンテナと呼びます。あとは必要な数だけ子要素を配置していくわけですが、現段階ではグリッドに関する指定を何も行っていないため、画面に表示される内容には変化が現れません。

HTMLとCSSの基本的な構成は以下の通りです。


<div class="container">
	<div class="item">Item 1</div>
	<div class="item">Item 2</div>
	<div class="item">Item 3</div>
	<div class="item">Item 4</div>
</div>

.container {
	display: grid;
	/* コンテナに指定するプロパティ */
}
.item {
	/* アイテムに指定するプロパティ */
}

グリッド・コンテナとアイテム

グリッド・コンテナに配置された直接の子要素は、自動的にグリッド・アイテムとして扱われます。アイテムは、コンテナが定義するグリッドに沿って配置されます。そのため、グリッド・レイアウトで最初に着手すべき作業は、コンテナ側でのグリッドの定義となります。

まずは、グリッドの定義を何も行っていない状態での挙動を確認しましょう。以下の例を操作して、プルダウンメニューの値をblockからgridに変えてみて下さい。

resizeに対応しているブラウザであれば、コンテナの右下にハンドルが表示されます。これを掴んでコンテナの寸法を変えた時に、子要素はどのように振る舞うでしょうか。

コンテナの寸法に合わせて子要素が伸縮するようになりました。なぜそのような挙動になるのかと言うと、コンテナがアイテムの数の分だけ、グリッドを作成したからです。

ここでは何も明示されていないため、行方向のグリッドが暗黙的に引かれました。ひとつの要素が一行に属しているため、通常のブロック要素のレイアウトと変わりません。

ただし、グリッドは親要素であるコンテナ側で定義されているため、グリッドに沿ってアイテムを配置するグリッド・レイアウトの中では、親要素の寸法を変えるとグリッドに張り付いているアイテムも一緒に移動するのです。

これがグリッド・レイアウトにおけるコンテナとアイテムの関係です。通常のフロー・レイアウトやフレックス・ボックスであれば、子要素には独自の寸法が指定でき、それが尊重される形で配置させます。しかし、グリッド・レイアウトは、コンテナに指定するグリッドそのものが基準であり、そのグリッドによってアイテムの位置や寸法が決まります。

グリッド・トラック

グリッド・レイアウトではグリッド・トラックという範囲を扱います。グリッド・トラックとは、コンテナの上に引かれた二本の線の間にある空間のことです。

グリッド・トラックは、grid-template-columnsおよびgrid-template-rowsで作成します。これらを一括で操作するショートハンド・プロパティも存在しますが、ここでは基本の動作を学びます。

これらのプロパティは、その名が示している通り、アイテムを配置するためのテンプレートを用意するものです。グリッド・トラックはアイテムを配置するまで目に見えませんが、イメージ化すると以下のようになります。

グリッド・トラックを引くと、必然的にコンテナの中が分割されます。ここにアイテムを配置していくことになるのですが、最初の例では垂直のトラックが指定されていなかったため、コンテナの全幅がひとつの列トラックとみなされていました。

グリッド・トラックは、コンテナの中に何本でも引くことができます。その寸法も自由に指定することが可能で、固定幅はもちろんのこと柔軟に伸縮する自動計算も指定できます。

以下の例を操作して、垂直方向のグリッド・トラックを足してみましょう。グリッド列の定義はgrid-template-columnsで行います。ここでは、アイテムの数に応じて自動的に比率を計算する1frという単位を使います。

垂直のグリッド・トラックを追加したところ、アイテムが自動的に並び替えられました。この段階では、アイテムの位置指定を行っていないため、トラックの隙間を埋めるように詰めて配置されたからです。

ここまでの操作で記述内容が変わっているのは、grid-template-columnsの一行だけです。この値に100px6emなどの数値を記述すれば、それがトラックの寸法となります。以下の内容は、コンテナに4列のグリッド・トラックを指定した時のものです。


<div class="container">
	<div class="item">Item 1</div>
	<div class="item">Item 2</div>
	<div class="item">Item 3</div>
	<div class="item">Item 4</div>
</div>

.container {
	display: grid;
	grid-template-columns: 1fr 1fr 1fr 1fr;
}
.item {
	/* アイテムに指定するプロパティ */
}

暗黙的なグリッドと明示的なグリッド

グリッド・トラックには、暗黙的なグリッドと明示的なグリッドがあります。最初に示したサンプルでは、コンテナに何のプロパティも指定されていなかったため、通常のフロー・レイアウトと同じように子要素が縦に並びました。

実は、この時すでにグリッド・トラックが作成されています。それが暗黙的なグリッドです。暗黙的なグリッドは、アイテムの配置数に応じてコンテナが自動的に作成するトラックです。制作者がプロパティで明示しなかった場合、必要に応じてブラウザが勝手に用意してくれます。

これに対して、明示的なグリッドは制作者があらかじめCSSで宣言するものです。明示的という言葉には、事前に用意しておくテンプレートという意味合いがあります。明示的なグリッドがあれば、コンテナはアイテムの数に関わらず指定されたトラックを引きます。そして、明示的なグリッドの数を超えてアイテムが配置された場合には、暗黙的なグリッドが自動的に補完します。

暗黙的なグリッド

  • 暗黙的なグリッドはアイテムの数に応じて自動的に作成されます。
  • 列方向の暗黙的なグリッドの動作はgrid-auto-columnsで操作できます。
  • 行方向の暗黙的なグリッドの動作はgrid-auto-rowsで操作できます。

明示的なグリッド

  • 明示的なグリッドはCSSで宣言します。
  • 列方向の明示的なグリッドはgrid-template-columnsで定義します。
  • 行方向の明示的なグリッドはgrid-template-rowsで定義します。

動作確認

以下の例は、grid-template-columnsで4つの列トラックを作成したコンテナの中に8つのアイテムを配置した時のものです。この場合、明示的なトラックの数を超過するアイテムが配置されたことになります。

最初の状態では、行の高さが定義されていないため暗黙的なトラックは自動計算で寸法が決まります。そこにgrid-auto-rows100pxの寸法を指定すると、暗黙的なトラックは固定幅で表示されます。

そして固定値を一行目の方にも指定すると、grid-template-rowsの明示が見当たらないため、この寸法が採用されます。さらに、三行目にあたる部分に固定幅を追加してみると、そこにはアイテムが存在しないため、何も描画されません。

トラックのサイズ指定

グリッド・トラックは様々な方法でサイズ指定が行えます。トラックのサイズ指定は、明示的なグリッドと暗黙的なグリッドどちらにも適用できます。基本的には、要素の寸法を定義する時と同じように、絶対値や相対値、キーワードなどを使って寸法を決めていきます。

複数のトラックを指定する場合は、プロパティの値を半角区切りのリスト型のデータで定義します。例えば、100pxのトラックを二本引きたい場合は100px 100pxと書きます。

自動計算

グリッド・トラックの寸法を自動計算で決定する場合は、autofrの単位を使います。基本的にはコンテナの寸法に合わせてトラックが伸縮します。レスポンシブデザインのように、事前に寸法が決まってない場合に有効です。


.grid_box {
	grid-template-columns: auto auto auto auto;
}

相対的な寸法

frの単位を使うと、トラックの数に応じてコンテナの空間を割り当てる比率を操作できます。以下の例は、先頭に2frのトラックを作成し、その後ろに1frのトラックを3本引いたものです。トラックの寸法は自動的に計算されますが、利用可能な空間を割り振る際に相対的な比率が考慮されます。


.grid_box {
	grid-template-columns: 2fr 1fr 1fr 1fr;
}

固定値による絶対的な寸法

ブロック要素のサイズを指定する時と同様に、トラックのサイズを絶対値で固定することもできます。以下の例は、先頭と末尾のトラックの寸法を絶対値で固定し、中間のトラックを伸縮可能な相対値にしたものです。

resizeに対応しているブラウザであれば、サンプルの右下にハンドルが表示されます。それを掴んでコンテナの幅を変えた時に、各トラックの寸法がどのように変わるのか確かめてみましょう。


.grid_box {
	grid-template-columns: 150px 1fr 2fr 5rem;
}

同一の値を繰り返す指定

同じ値を繰り返して指定する場合、特別な関数が使えます。repeat()という記法を使うと、複数のトラックの寸法を一括で指定できます。この関数の値は、繰り返す数とトラックの寸法です。それをかっこの中にカンマ区切りで記述します。

以下の例は、8つのトラックを自動計算による寸法で作成したものです。この場合、トラックの数が多いので、autoを連続して記述するよりもrepeat()表記の方がスマートです。


.grid_box {
	grid-template-columns: repeat(8, auto);
}

repeat()記法は、値の列記の一部にも使えます。上記のサンプルの両端だけを固定値にする場合は、以下のような指定になります。


.grid_box {
	grid-template-columns: 100px repeat(6, auto) 100px;
}

repeat()記法だけでパターンを作り上げることもできます。カンマ区切りの後に寸法を連続して記述すると、そのパターンを指定された数だけ繰り返します。そのため、二つの寸法を用いる際には繰り返す数を半分に調整します。

以下の例は、自動寸法と固定値の二つの値の繰り返しです。アイテムを8個配置するため、繰り返す数は4回です。


.grid_box {
	grid-template-columns: repeat(4, auto 50px);
}

グリッド・ライン(グリッド線)

グリッド・ラインは、グリッド・トラックを作成した時に生まれる副産物です。グリッド・トラックは直接と直線に挟まれた範囲のことですが、トラック同士の境界にあたる部分をグリッド・ラインと呼びます。

グリッド・ラインを明示的に作成することはできません。その変わり、グリッド・トラックを作成すると、そこには自動的にグリッド・ラインが存在することになります。

グリッド・ラインの位置は番号で表されます。水平方向に引かれたグリッド・ラインは上辺から、垂直方向に引かれたグリッド・ラインは左辺から順番に数えます。この順序は書字方向に従うため、取り扱う言語やdirectionプロパティの値が変わる場合は注意が必要です。

以下の例は、縦と横に2本ずつグリッド・トラックを作成したコンテナです。この場合、トラックの辺に隣接する境界が3本ずつ存在することになります。ここに番号が自動的に振られてアイテムを配置する時の基準として使えるようになります。

グリッド・ラインを指定したアイテムの配置

グリッド・ラインの番号を指定することでアイテムを任意の場所に配置できます。この方法を利用する場合、アイテムの辺がグリッド・ラインのどの番号にフィットするのかを明示します。

ここで登場するプロパティには以下のものがあります。基本的には行方向あるいは列方向の、開始位置や終了位置を指定することで、アイテムの配置をコントロールします。

例えば、先ほどの4つに区切られたコンテナの中にアイテムを配置する場合に、ひとつ目のアイテムに対して、行の開始位置を2、列の開始位置も2に設定すると、以下のようになります。


.grid_box {
	display: grid;
	grid-template: 1fr 1fr / 1fr 1fr;
	height: 100%;
	background-color: #fff;
}
.grid_box div {
	padding: 5px;
	border-top: 2px solid #09f;
	border-bottom: 2px solid #09f;
	border-right: 2px solid #f90;
	border-left: 2px solid #f90;
}
#item1 {
	grid-column-start: 2;
	grid-row-start: 2;
	background-color: rgba(0, 255, 102, .5);
}

<div class="grid_box">
	<div id="item1">A</div>
	<div>B</div>
	<div>C</div>
	<div>D</div>
</div>

グリッド・セル

グリッド・セルは、行トラックと列トラックが交差した場所に現れる最小単位です。これはテーブル(表)のセルと同じ意味合いを持ちます。コンテナは、displayプロパティの値にgridが指定された時点で暗黙的なグリッドを持ちますが、仮に子要素がひとつしか含まれなかった場合、グリッド・トラックは一行および一列となり、結果としてグリッド・セルがひとつだけ存在することになります。

以下の例は、明示的に3列のトラックを与えたコンテナに5つのアイテムを配置した場合の挙動です。アイテムの数は3列を超過するため、自動的に折り返して暗黙的なトラックが作成されます。暗黙的なトラックのサイズは自動計算にしているため、コンテナの寸法に合わせてその隙間を埋めるようになっています。

この時、1つのアイテムは1つのグリッド・セルに収まります。配置に関するプロパティを何も指定していなければ、アイテムは順序通りに各セルに配置され、その最小単位を守ります。

グリッド・エリア

グリッド・エリアは、複数のグリッド・セルに渡って明示できる範囲のことです。グリッド・セルはグリッド・トラックが作成された時点で暗黙的に存在しますが、グリッド・エリアはコンテナあるいはアイテムのプロパティで作成することになります。

グリッド・エリアは正方形でなければなりません。最小範囲はセルひとつ分となります。そして複数のセルをまたいで固有のグリッド・エリアとして扱いたい範囲を決めることになりますが、L字に曲がった範囲や飛び石状態の範囲を作ることはできません。

アイテム側でグリッド・エリアを作成する

以下の例では、明示的に3列のトラックを与えたコンテナに6つのアイテムを配置しています。ここで、Aのアイテムにgrid-areaプロパティを追加し、複数のセルにまたがる配置を指定します。すると、Aのアイテムだけが他のアイテムを押しのけて大きくなりました。これがグリッド・エリアです。

アイテムに付加したプロパティの値は以下の通りです。1つ目の値はgrid-row-startに該当し、2つ目の値はgrid-column-startに該当します。これはアイテムの開始位置に対応するグリッド・ラインの番号です。そして3つ目の値はgrid-row-end、4つ目の値がgrid-column-endというように、アイテムの辺の終了位置を表します。

#ga1 {
	grid-area: 1 / 1 / 3 / 3;
}

この値が1 / 1 / 2 / 2であれば、何も指定しない時と同じように単一のセルに収まります。ここで2番目のグリッド・ラインを超えて3番目のグリッド・ラインの番号を指定しているので、複数のセルをひとつのグリッド・エリアとして扱っているのです。

コンテナ側でグリッド・エリアを作成する

グリッド・エリアをコンテナ側で作成する場合は、grid-template-areasというプロパティを使います。このプロパティを使うと、グリッド・エリアに任意の名称を与えることができます。その名称を使ってアイテムを配置できるため、グリッド・ラインの番号による指定よりも直感的に操作できます。

以下の内容は、名前つきのグリッド・エリアを作成して、アイテムをそこに配置する場合の参考例です。グリッド・トラックの構成は2列と3行ですが、ヘッダーとフッターが二つのグリッド・セルを使って、ひとつのグリッド・エリアにしています。


.grid_box {
	overflow: auto;
	padding: 1rem;
	background: #eee;
	text-align: center;
	resize: horizontal;
	display: grid;
	grid-template-areas: "header header"
						 "main nav"
						 "footer footer";
	grid-template-rows: 50px 1fr 50px;
	grid-template-columns: 1fr 150px;
	gap: 10px;
}
.grid_box > div {
	padding: .3rem;
	border: 1px solid #666;
	background: #09f;
	color: #fff;
}
#item_header {
	grid-area: header;
	background-color: #f60;
}
#item_main {
	grid-area: main;
	background-color: #fc0;
	min-height: 150px;
}
#item_nav {
	grid-area: nav;
	background-color: #6f0;
	min-height: 100px;
}
#item_footer {
	grid-area: footer;
	background-color: #09f;
}

<div class="grid_box">
	<div id="item_header">header</div>
	<div id="item_main">main</div>
	<div id="item_nav">nav</div>
	<div id="item_footer">footer</div>
</div>

グリッド・セルの間隔

グリッドのセル同士の間隔は、column-gapおよびrow-gapで指定します。この二つのプロパティにはgapというショートハンドが用意されています。ギャップはセル同士が隣接する辺の隙間を定義するものです。コンテナとセルの辺が接触する外側の間隔には影響しません。

以下の例は、gapの値を変えた場合に、セル同士の間隔がどのように表示されるのかを確認するものです。全ての方向を一括で操作する場合は1つの値、行と列に異なる寸法を指定する場合は2つの値を扱います。

グリッドを入れ子にする

グリッド・アイテムのdisplayプロパティにgridを指定するとコンテナになります。つまり、グリッドは入れ子構造にできます。グリッドを入れ子にするメリットは、親要素のコンテナで作成したトラックから切り離してレイアウトを行えることです。

以下の例は、先頭のアイテムにgrid-areaを指定し、複数のセルにまたがるグリッド・エリアに配置しています。そのアイテムにdisplayプロパティのgridを加え、コンテナ化した上で6つのアイテムを配置しています。

この時、入れ子の中に含まれるアイテムのレイアウトは、親のコンテナのトラックではなく、コンテナ化したアイテムのトラックに従っています。


.grid_box {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	grid-auto-rows: auto;
	height: 100%;
	background-color: #fff;
}
.item {
	padding: 5px;
	border: 2px solid #09f;
}
#ga1 {
	grid-area: 1 / 1 / 3 / 3;
}

<div class="grid_box">
	<div class="item" id="ga1">
		<div class="grid_box">
			<div class="item">1</div>
			<div class="item">2</div>
			<div class="item">3</div>
			<div class="item">4</div>
			<div class="item">5</div>
			<div class="item">6</div>
		</div>
	</div>
	<div class="item">B</div>
	<div class="item">C</div>
	<div class="item">D</div>
	<div class="item">E</div>
	<div class="item">F</div>
</div>

フレックス・ボックスとの比較

グリッド・レイアウトとフレックス・ボックスを比較した場合、どちらを採用したらいいのか悩むことがあります。この二つの機能は、どちらか一方が優れているということはなく、それぞれに適材適所があります。場面に応じて使い分けができるように、以下の点を押さえておきましょう。

グリッド・レイアウトは、グリッド・トラックの集合を作成して格子状の平面にアイテムを配置していく機能です。そのため、複数の行や列を扱う多次元のレイアウトに向いています。

一方のフレックス・ボックスは、単一の行や列を扱う場面が得意です。リストやナビゲーションを横一列に並べたり、条件によって行方向の配置と列方向の配置を切り替えるようなデザインに向いています。

そして、アイテムの扱い方にも違いがあります。グリッド・レイアウトのアイテムは、コンテナ側に作成されたトラックに貼り付けるものです。アイテムの寸法や伸縮は完全にコンテナに依存しています。

フレックス・ボックスのアイテムは、コンテナの空間に流し込まれるものです。そこには主軸と交差軸、先頭と末尾という流れがあり、その規則の中でどのように振る舞うのかアイテム自身で決められます。

どちらの仕組みを使ったとしても、最終的に同じ結果が得られるのであれば問題ありません。ただし、得意分野を扱った方が作業が楽になるという点は間違いありません。実際の制作で迷わないようにしたい場合は、フレックス・ボックスの解説も合わせて参照して下さい。

CSSリファレンス一覧