ブログ

ここを意識したらCSS Gridの扱いが少しだけ上手くなった話

少し前まで苦手意識のあったCSS Grid Layoutですが、ここ半年くらいで実務で使用する機会が増え気がついたら苦手意識が薄まっていました。

間違いなく「作って学ぶ HTML+CSSグリッドレイアウト」を読んだおかげだと思いますが、Gridを使う上で注意点やコツみたいなものはあるのかなと感じています。

CSS Gridを使用する時に意識していることや、知っていると便利だなと思うことをまとめてみます。

autoと1frの動作の違いを理解する

auto1frはどちらもCSS Gridレイアウトで列幅や行幅を指定する際に使用されますが、それぞれの用途や動作は似ているようで異なります。以下の違いを理解していると扱いやすくなります。

特徴auto1fr
サイズの決定基準コンテンツのサイズ利用可能なスペース(を分割)
コンテンツが多い場合の動作列(行)幅が広がる列(行)幅は固定(スクロールが発生することもある)

特にサイズの決定基準の理解が重要です。以下はauto1frのコンテンツサイズが決定されるそれぞれの基準を表した例です。

autoと1frのサイズの決定基準

①のようにauto1frを同時に指定した場合それぞれの幅は、autoはコンテンツサイズに、1frは残りの利用可能なスペース全体となります。

一方で②③のように、auto1frを連続して指定した場合は以下のようになります。

grid-template-columns: auto autoでは各列の幅はその列に含まれるコンテンツ(今回はテキスト)の長さに基づいて決定されます。「短いテキスト」の幅は短くなり、「非常に長いテキスト」の幅は長くなります。

grid-template-columns: 1fr 1frでは2つの列が利用可能なスペースを均等に分割します。コンテンツの長さに関係なく、両方の列は同じ幅を持ちます。frの比率が異なれば、その比率で利用可能なスペースを分割します。

まとめるとautoは中のコンテンツに依存して、1frはグリッドコンテナの利用可能なスペースに依存して列幅や行幅が決定されます。

全てのトラックサイズにautoを指定する際の注意点

グリッドを使用していると予期せずに行幅(または列幅)が広がってしまうようなケースがあります。例えば以下のような事例です。

行幅が広がった例

タイトルの高さはコンテンツの高さに依存するのが自然だと思いますが、タイトルの領域を表す水色のエリアが広がってしまっています。

このケースでは、グリッドコンテナの高さは画像のアスペクト比と幅に基づいて決定しています。そしてタイトルとテキストのgrid-template-rowsautoが指定されていると、.title.textの高さはコンテンツに基づきつつ、利用可能な高さに合わせて分配されるという挙動をとります。

.titleの高さをコンテンツの高さと同じにしたい場合は以下のように.textのトラックサイズに1rを指定します。

行幅を調整した例

グリッドアイテムの片方が画像などの場合は高さ(または幅)が画像にもとづいて決定するため、もう片方の列(または行)のいずれかのグリッドアイテムで1frを指定することが重要です。

意外とハマるポイントなので基礎をしっかり理解しておくと良いでしょう。

コンテンツのオーバーフローに注意する

CSS Gridレイアウトを使用していると、思いがけずグリッドのコンテンツがオーバーフロー(body要素をはみ出て横スクロール)することがあります。例えば以下のような事例です。

オーバーフローした例

上記はpreタグを使用した事例ですがその他にもグリッドアイテムの中に、カルーセル大きな画像がある場合にオーバーフローが発生しやすい印象です。

この現象はgrid-template-columns1frまたはauto;を指定していることが原因です。次のようにコードを変更することでオーバーフローを防ぐことができます。

style.css
.grid-container {
display: grid;
/* grid-template-columns: 1fr; */
grid-template-columns: minmax(0, 1fr);
}

1frでオーバーフローが発生する原因

1frはminmax(auto, 1fr)と等価であり、autoの最小値は、グリッドアイテムのmin-contentサイズ(=内容が折り返されずに表示される最小サイズ)となります。

preタグは内容が折り返されないため、min-contentサイズ=テキスト全体の幅となります。その結果、グリッドトラックの幅が拡大し、オーバーフローが発生します。

一方でgrid-template-columns: minmax(0, 1fr);では最小値を0pxと明示的に指定しています。最小値はmin-contentサイズではなく0pxが採用され、結果としてpreタグの内容がはみ出してもグリッドトラック自体はコンテナ幅に収まります。

Tailwindcssでもグリッドのトラックサイズにはminmax(0, 1fr)が採用されています。

Tailwindcssのグリッドのトラックサイズ
Tailwindcssのグリッドのトラックサイズ

1frを使用する際には明示的に最小値を指定した方が無難なようです。

frは中途半端な数値でOK

gird-template-column等で使用するfrは簡約化した数値でなくても動作します。そのためデザインカンプに記載されている数値をそのままfrに当てはめると、その比率を保ったまま拡大・縮小してくれます。

style.css
/* 最大公約数で簡約化した例 */
.col {
gird-template-column: 5fr 3fr;
}
/* デザインカンプの値をそのまま反映した例 */
.col {
gird-template-column: 420fr 252fr;
}

以前はなぜか最大公約数で簡約化した数値を記述する必要があると思い込んでいたため、比率計算機などでわざわざ計算を行った上で入力していました。

ただ実際には簡約化した数値でなくても動くので、そのままの値を当てはめるようにしています。

grid-templateで一括指定を行う

grid-templateを使用することで以下のプロパティを一括で指定することができます。

  • grid-template-areas
  • grid-template-columns
  • grid-template-rows

よくあるカードのコンポーネントで、一括指定を行わなかった場合は以下のようになります。

個別指定の例
.card {
display: grid;
grid-template-areas:
'img title'
'img text'
'img button'
;
grid-template-columns: 3fr 2fr;
grid-template-rows: auto auto 1fr;
}

これをgrid-templateで一括して書くと次のようになります。

一括指定の例
.card {
display: grid;
grid-template:
'img title' auto
'img text' auto
'img button' 1fr
/ 3fr 2fr;
}

かなりスッキリしましたよね。

エリアの記述の下側と右側にそれぞれのトラックサイズを書くので、別々に書いた時よりもトラックサイズが読み取りやすく感じます。gridでエリアを定義する際にはgrid-templateの一括指定を積極的に使用するようにしています。

grid-templateでエリアを定義する基準

display: gird;を使用する際にはgrid-template-areasを使用してエリアを定義する方法としない方法の2種類の記述パターンがあります。この違いについて見ていきます。

以下は各セルのエリアを定義した例です。

エリアを定義した場合
.card {
display: grid;
grid-template:
'img title' auto
'img text' auto
'img button' 1fr
/ 3fr 2fr;
}
.img {
grid-area: img;
}
.title {
grid-area: title;
}
.text {
grid-area: text;
}
.button {
grid-area: button;
}

一方で下記は各セルのエリアを定義しない例です。grid-columngrid-rowを使って各アイテムの割り当てられるトラックを指定しています。

columns/rowsを使う場合
.card {
display: grid;
grid-template-columns: 3fr 2fr;
grid-template-rows: auto auto 1fr;
}
.img {
grid-column: 1 / 2;
grid-row: 1 / 4;
}
.title {
grid-column: 2 / 3;
grid-row: 1 / 2;
}
.text {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
.button {
grid-column: 2 / 3;
grid-row: 3 / 4;
}

直感的に見て理解しやすいのは「エリアを定義した例」かなと感じます。エリアを定義した場合のメリットとデメリットについて考えてみました。

メリットデメリット
  • レイアウトの構造が一目で分かり可読性が向上する。
  • grid-templateを変更するだけで、レスポンシブ対応やレイアウト変更が容易になる。
  • グリッドコンテナがレイアウト全体を管理するため、コンポーネント設計と相性が良い。
  • グリッドアイテムごとにgrid-areaを指定する必要があり、コード量が増える。
  • 単純なグリッド構成では、過剰な設定となり手間が増える。

エリアの定義はシンプルなレイアウトでは過剰になってしまいますが、複雑なレイアウトではメリットが多いと感じます。私の場合、次のような使い分けをしています。

  • 半数以上のグリッドアイテムにトラックを指定する必要がある:エリアを定義する
  • 上記以外:エリアを定義しない(columns/rowsまたはorderで対応)

要素の自動配置のみでレイアウトの大部分を組める場合は、エリアを定義しないことが多いです。

実際にはその他にも、レスポンシブでレイアウトが変わるかや、将来変更の可能性があるのかなども考慮し総合的に判断しています。

グリッドアイテム間の余白をピリオドで設定する

グリッドアイテムの間の余白を設定するには以下のようにgapを使用することが多いと思います。

gapで余白を設定
.card {
display: grid;
grid-template:
'img title' auto
'img text' auto
'img button' 1fr
/ 3fr 2fr;
row-gap: 20px;
column-gap: 40px;
}

よくあるのが1〜2行目の間の余白と、2〜3行目の間の余白を異なる値で設定したいというパターンです。

この場合row-gapを指定したままグリッドアイテム側で余白を調整する方法も良いですが、以下のようにtemplateの中でピリオドを使用することで個別に余白を設定することもできます。

ピリオドで余白を設定
.card {
display: grid;
grid-template:
'img title' auto
'. .' 20px
'img text' auto
'. .' 30px
'img button' 1fr
/ 3fr 2fr;
column-gap: 40px;
}

余白の値が共通の場合にはrow-gapまたはcolumn-gapを、余白の値が異なる場合にはピリオドを使うと良いかもしれません。

まとめ

今回紹介したレイアウトの大部分はdisplay: flex;などの他のプロパティでも代用可能です。ただレイアウトの再現方法については選択肢が多いに越したことはないと考えています。

複数ある選択肢の中からそのデザインに最適なプロパティを選び実装することが、コーダーの仕事の醍醐味のような気がします(この辺りが難しくもあり楽しくもあるコーダーの役割だと感じています)。

特定のプロパティに依存せず様々なレイアウトを表現できるよう日々勉強です。

参考サイト