バナー

この教材は「ギャクサン」というWeb制作学習カリキュラムの一部です。

はじめての方はこちらからご覧ください👉

更新日

10|CSS Gridとflexを使い分けをマスターしよう

web兄さん

今までレイアウトは全てdisplay: flexを使用してきましたが、今後どんどん活用されていくであろうCSS Gridを使用したレイアウトもマスターしていきましょう。

Gridの学習の流れ

GridはCSSの中でもレベルの高い知識の1つです。実はプロのコーダーさんでもGridを使いこなせる人はそこまで多くありません。(逆に言えばGridが使えなくても仕事としては成り立ちます

順を追って身につけていく必要があるので、以下の流れで取り組んでいきましょう。

  1. こちらの「CSS Grid 入門編」で基礎を身につける
  2. 学んだ知識を元にこの講座で実務レベルの実装を身につける
  3. 最後に「FlexboxとCSS Gridの違い」をおさらいする

基礎を終えたら実務に落とし込んでいこう

今回は以下の6つの例を元に実務レベルで扱えるように基礎を昇華させていきます👍

①左右反転
②カードレイアウト
③セルの端にそれぞれの要素を配置
④タイル風
⑤サイドバー+記事レイアウト
⑥コンテンツが少なくてもページの高さを保持

課題用のフォルダをダウンロード

このフォルダには6つの課題サンプルの課題・解答ファイルが入っています。

  • 08_grid > 01〜06のフォルダ
  • 01〜06 > Q:課題用のフォルダ
  • 01〜06 > A:解答用のフォルダ
web兄さん
このフォルダを解説と解答用のフォルダのstyle.cssを見比べながらコードを書いていきましょう。

01|左右反転のレイアウト

動画

解説|grid-rowとgrid-columnの省略記法

隣あったライン番号は省略できる

入門編では、アイテムの配置場所を指定する、grid-rowgrid-column

開始ライン番号 / 終了ライン番号

という書き方をしていました。(例:列のライン1番目から2番目であればgrid-column:1/2

しかし、隣あったラインに配置する場合は終了番号を省略することができます。


  .grid__img._reverse{
    grid-row: 2;
    grid-column: 2;
  }

反転させている画像の方のアイテムは

「列の2〜3番目のライン」と「行の2〜3番目のライン」に配置したいので左記のようなコードになっています。

解説|反転レイアウトは本来flex向き

上記の省略記法を説明するために今回のレイアウトを採用しましたが、本来反転レイアウトはflex-directioncolumn-reverserow-reverseを使うのが理想的です。

なぜなら今回のようにカラム幅が「1:1」の関係にないことが多いためです。CSS Gridはラインに沿って配置される特性上、2行目、3行目と行ごとの列の幅が変動するレイアウトの実装は不向きです。

つまり、子要素の幅によってカラム幅が決まるレイアウトの場合はflexが向いています。

02|カードレイアウト

web兄さん
デモを見るとわかりますが、メディアクエリを使うことなくブラウザ幅の縮小に合わせて1〜3列に柔軟に変化させています。

動画

解説|repeat(auto-fill,minmax(250px,1fr));

今回のポイントはまさしくこれです。

grid-template-columns: repeat(auto-fill,minmax(250px,1fr));

入門編で出てきたrepeat関数の他にauto-fillminmax関数と全部で3つの要素が絡んで実装されているのでじっくり考えないと頭がこんがらがります。

web兄さん
一つ一つ個別に解説した後、再度まとめて解説をします。

minmax()

主にgrid-template-columnで使用するこの関数にはminmax(最小幅,最大幅)を記載します。

今回ではminmax(250px,1fr)とあるため、アイテムは「250pxまでしか小さくならず、最大は1frまで広がる」という記述です。

最大幅が1frということは、親のコンテナ幅が広がればどこまでも均等に拡大するということになります。

auto-fill

auto-fillrepeat関数で使用する値の1つで、「コンテナの中にアイテムを敷き詰めるだけ敷き詰めたい時」に使います。

例えば以下のコードの場合、親要素に300pxの幅の列を作り、敷き詰めるだけアイテムを敷き詰めますが、60pxの余白にはアイテムが入り切らないため、次の行へ折り返されます。

.grid{
  width: 960px;
  display: grid;
  grid-template-column: repeat(auto-fill,300px)
}
web兄さん
イメージとしてはflex-wrapで折り返すことに近いですね。

repeat()

こちらはおさらい程度ですが、repeat関数はrepeat(繰り返す回数,繰り返す値)という記述の仕方でしたね。

repeat(auto-fill,minmax(250px,1fr))を訳すと…

さて、ここまでの解説をまとめて実際のコードを訳していきましょう。

  1. まず、repeat()で繰り返される回数auto-fillなので「敷き詰める要素の数だけ繰り返し」となります。
  2. 次にrepeat()で繰り返される値(=サイズ)は最小250px、最大1fr
  3. つまり、親要素のサイズ(960px)を縮小した場合は、1つのアイテムが250pxまで縮むが、それ以下には縮まないため、要素が折り返される
  4. 逆に、親要素のサイズ(960px)を拡大した場合は、250px以上のサイズで敷き詰められるだけアイテムが敷き詰められる

となります。(言葉にすると結構ややこしいですね…)


例えば、コンテナの幅が750pxの場合は、750px÷250pxで3列になりそうですよね。

しかし、gap: 15pxの余白が設定されているため、3列表示するには最低でも余白を含んだ250px × 3 + 15px × 2 = 780pxの幅が必要となり、アイテムを並べる幅が足りず2列となります。

そして、minmax(250px,1fr)で最大幅が均等に広がるようになっているため、親要素いっぱいにアイテムが広がるというわけです。


1列の時も同じ理屈です。以下はブラウザ幅が500pxですので2列並びそうですが、.l-containerpadding: 0 1.6remの左右の余白がついている関係で2列表示する幅が足りません。

そのため1列の表示になりますが、minmax(250px,1fr)で親要素いっぱいに自動で広がるというわけです。

web兄さん
記述が複数絡んでいる分ややこしいのですが、デモを確認しながら何度か読み返すと理解できてくると思います!

弱点はブレイクポイントのコントロールが難しいこと

この実装方法は実に画期的で、こだわらなければグリッドレイアウトがたった一行で済みます。

しかし、「1024px以下は3列で、768px以下は2列にしたい」というブレイクポイントごとの指定はできないという課題があります。

そのため、任意のブレイクポイントでのカラム数をコントロールしたい場合は繰り返しにauto-fitを使わず明示的な数字を入れる必要があります。

@media screen and (min-width: 768px),print { /*768px以上は2列*/
	.cardList {
		grid-template-columns: repeat(2,1fr);
	}
}

@media screen and (min-width: 992px),print { /*992px以上は3列*/
	.cardList {
		grid-template-columns: repeat(3,1fr);
	}
}

@media screen and (min-width: 1200px),print { /*1200px以上は4列*/
	.cardList {
		grid-template-columns: repeat(4,1fr);
	}
}

03|グリッドの端にそれぞれの要素を配置

web兄さん
一見Gridを使ったレイアウトに見えないかもしれませんが、デモのグリッドを可視化すると以下のような配置になっています。
  • 画像は左
  • テキストは右上
  • ボタンは右下

という配置になっていますよね。

また、このレイアウトの良いところは少ないHTMLでテキストの増量やレスポンシブ時のボタンの折り返しなど様々なケースにおいて綺麗な見た目を保てることです。

動画

解説|ボタンをセルの右下に配置する

.c-product{
  grid-template-columns: 336px 1fr;
} 

まず列の幅ですが、画像は常に幅を336pxで固定し、テキストやボタンの列が縮小するようにしています。


@media (min-width: 768px) {
  .c-product__footer{
    align-self: flex-end; /* 高さを要素の高さにしトラックの下に配置 */
  }
}

続いて、align-self: flex-startですが、flexの時にコンテナの高さに合わせてアイテムが伸びることを防ぐ時に使用しますよね。

Gridでも同じ目的で使用します。

デフォルトでは、アイテムの幅と高さはラインのエリアに沿って決まるため、align-self: flex-startを設定していない場合、ボタンは上記のようにエリアいっぱいに広がってしまいます。


実は、flexでしか使えないと思っていたプロパティのいくつかはgridでも使用することができます。

プロパティ対象役割
justify-contentコンテナ(親)セル自体の主軸に対しての水平方向の位置
align-itemsコンテナ(親)セル内の主軸に対しての垂直方向の位置
align-selfアイテム(子)個別アイテムのセル内の垂直方向の位置
orderアイテム(子)アイテムの順番の指定

このあたりはよく使用しますよね。他にもflexで使っている配置系のプロパティはgridでも使用することができることを覚えておきましょう。

解説|ボタンの2列→1列をスマートに実装する

.c-product__footer{
  display: grid;
  justify-content: space-between;
  grid-template-columns: repeat(auto-fill, minmax(250px,1fr));
  gap: 1.6rem;
}

02|カードレイアウトでも出てきたrepeat(auto-fill, minmax(250px,1fr))をボタンにも使うことで、2列に並んだボタンのサイズが250px以下になるタイミングで1列になるようにしています。


gapはアイテムの間にしか余白を作らない

またgap: 1.6remで余白を確保することで、横並びから縦並びになったときでも、marginを設定し直すなどの手間がありません。

04|タイル風レイアウト

ブラウザ幅が広い時は複雑なレイアウトですが、縮小するに連れて4列→3列→2列→1列と変化していきます。

動画

解説|ボックスのタイル風→4列→3列→2列→1列のレイアウト変化

.sns{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px,1fr));
}
@media (min-width:768px) {
  .sns{
    grid-template-rows: repeat(3,160px);
  }
}

メディアクエリを使用して、

  • デスクトップの時は3列*160pxで固定
  • タブレット以下はお馴染みのrepeat()で自動折り返し

という制御をしています。このように、メディアクエリを活用することで柔軟なレイアウトに対応することができます。

解説|auto-fitとauto-fillの違い

気付きづらいのですが、今回はauto-fillではなくauto-fitになっています。

  grid-template-columns: repeat(auto-fit, minmax(160px,1fr));

混乱を防ぐために「敷き詰める時はauto-fill」と解説しましたが、実はauto-fitが適切な場合もあります。


まず、今回auto-fillとした場合にどう変化するかを見てみましょう。

セルが広がらず、最小幅として設定している160pxの3列表示になってしまいました。

  grid-template-columns: repeat(auto-fit, minmax(160px,1fr));

一見バグのように見えますが、これがauto-fillの本来の挙動です。グリッドを可視化するとわかるのですが、160px空白セルが敷き詰められて実質6列表示になっています。

次にauto-fitの場合のグリッドを可視化してみます。

セルがいっぱいに広がってきちんと表示されていますよね。

違いを整理すると…

余白の埋め方
auto-fill中身がないとしてもセルを埋めるだけ埋める
auto-fit余白を埋めるように、アイテムの入ったセルが広がる

ここまででピンとこない方は「auto-fill auto-fit 違い」で検索してみてください。この違いについては解説記事が多くあるのですぐ理解できるはずです👍

解説|Gridを使用した上下左右中央配置

上下左右の中央配置と言えば、現在は以下が主流ですよね。

.grid{
  display:flex;
  justify-content: center;
  align-items: center;
}

しかし、これもCSS Gridを使えば簡単に再現することができます。


.sns__box{
  display: grid;
  place-items: center;  /* 上下左右中央寄せ */
  aspect-ratio: 1/1; /* 正方形に保つ */
  border: solid 0.5px #B4B7BB;
  height: 100%;
}

3行目のplace-items: centerがそうです。

実はこのコードはアイテム内の配置をコントロールする

  1. align-items: center(交差軸)
  2. justify-items: center(主軸)

の2つのプロパティの一括指定となります。(表記が全く違うのがややこしい…)

そのため、以下でも同じように上下左右中央寄せになります。

.sns__box{
  display: grid;
  align-items: center
  justify-items: center;
}

05|サイドバー+記事レイアウト

よくあるサイドバーと記事部分に分かれたレイアウトに加えて、スクロール固定のSNSシェアボタンがついているパターンです。

動画

解説|grid-template-areasでエリアに名前をつける

入門編で出てこなかったgrid-template-areasというプロパティが出てきましたね。

.post{
  grid-template-areas: "side main sns";
  grid-template-columns: 240px 1fr 56px;
}
エリア

一旦「エリア」のおさらいです。

gridで分けた任意のセルの範囲をエリアと呼ぶのでしたね。

このエリアに名前をつけてアイテムを配置するのがgrid-template-areasの役割です。


以前までは、grid-rowgrid-columnを使い、何番目のライン間にアイテムを配置するかを指定していました。先ほどのタイル風レイアウトを例に上げると以下の通りですね。

しかし、このエリアごとに名前をつけて配置場所を名前で指定できるとしたらどうでしょうか?


まずは9分割されたセルのエリアに名前をつけてみます。

.sns{
    grid-template-rows: repeat(3,160px);
    grid-template-areas: 
      "tiktok instagram twitter"
      "youtube instagram pinterest"
      "youtube facebook facebook";
  }

上記と同じようにエリアを分けたい場合、コンテナに対してgrid-template-areasで次のように記述します。

エリア名は必ず文字列で記載し、区切ったセルの数だけエリア名を書くことに注目してください。

youtubefacebookのようにセルが複数またいでいる場合は複数記述します。


.sns__box._tiktok{
  grid-area: tiktok;
}

.sns__box._instagram{
  grid-area: instagram;
}

.sns__box._twitter{
  grid-area: twitter;
}

.sns__box._pinterest{
  grid-area: pinterest;
}

.sns__box._youtube{
  grid-area: youtube;
}

.sns__box._facebook{
  grid-area: facebook;
}

コンテナにエリア名をつけたら、次はアイテムごとにどのエリアに配置するかを指定します(これがgrid-rowgrid-columnと同じ役目)

例えば「instagram」のエリアに配置したい場合はgrid-area: instagramと書きます。

ポイントはこの時はエリア名をクォーテーションで囲まないことです。


こうすることでgrid-rowgrid-columnで配置した時と同じような配置を再現できます。


だいぶ遠回りをしてしまいましたが、改めて05のコードに戻ります。

.post{
  grid-template-areas: "side main sns";
  grid-template-columns: 240px 1fr 56px;
}

これまでの内容を踏まえるとこのコードは"side main sns"という3つのエリア分けをしていることになります。

この実装の良いところは、エリア名をつけて配置しているため、HTMLの構造を気にしなくてもいいところです。

スマホの時に「SNSボタン → 記事 → サイドバー」という縦の並びにしたいためHTMLの構造は以下のようになっています。

<div class="post__sns">
  ~~~~~~~
</div>
<div class="post__main">
  ~~~~~~~
</div>
<div class="post__side">
  ~~~~~~~
</div>

この構造の場合、デスクトップ横並びにしようとすると、通常ならSNSボタンが左、記事が真ん中、サイドバーが右となってしまいますが、気にすることなく好きな場所に配置することができます。

解説|グリッドレイアウトの中だけ追尾される要素

スクロール固定されたSNSシェアボタンはグリッドレイアウトの領域でのみついてくるため、スクロール値の取得などの煩わしい処理が必要ありません。

サイドバーの高さが記事の高さに合わせて伸びるため、そのサイドバーの中でposition: stickyをつけることで、グリッドレイアウトの中だけ追尾させることができます。

06|コンテンツが少なくてもページの高さを保持

これもGridを使う上で結構有名な実装方法の一つです。

例えばお問い合わせの完了ページなどでコンテンツの量が少ないと高さが足りず、ちょっとカッコ悪い見た目になってしまいます。

コンテンツが足りないと不恰好にな見た目に…
Gridを使って画面の高さを最低限キープする

gridを使用することで、どんなにコンテンツが少ない状況でも最低限、ページの高さ異常はキープしてくれるように実装することができます。

動画

解説|auto1frを組み合わせる

このレイアウトの仕組みは非常にシンプルです。

縦に並べた要素の幅をコントロールするので、今回はgrid-template-rowsで行の幅を指定します。

この時にポイントとなるのが、auto1frの違いです。

1frは列や行の幅に合わせて自動で広がるものですが、autoは要素の高さ分までしか広がりません。

.l-wrapper{
  display: grid;
  grid-template-rows: auto 1fr auto; /* header main footer */
  min-height: 100vh;
}

コードを見ると、コンテナの高さにmin-height:100vh(= 最低でもブラウザの高さを保持)を指定指定しています。

そのため、headerfooterに関しては要素が持つ高さ分となりますが、余った分はmainの領域が広がり、コンテンツの中身が足りていなくても高さを保持できるというロジックです。

横幅も設定しておくとベター

<pre>が含まれているとはみ出すので、grid-template-columns: 100%で列も指定してあげることで回避できます。