gapの余白指定が便利! gridとflexでできる新しいCSSレイアウト手法

152
143

CSSのgapプロパティーは余白を指定できる新しめの手法です。余白といえば、marginプロパティーやpaddingプロパティーを思い浮かべる方が多いと思いますが、CSS GridやFlexboxでgapプロパティーを使うと柔軟にレイアウトを組めます。もともとgapプロパティーはCSS Gridでのみ利用できていましたが、macOS Safari 14.1およびSafari on iOS 14.6からFlexboxでもgapプロパティーが使えるようになりました。

この記事ではFlexbox、CSS Gridで利用できるgapプロパティーを活用したレイアウト手法とそのメリットを紹介します。今までmarginプロパティーで要素間の余白を調整していたものもgapプロパティーで柔軟に対応できる場面もあります。なお、下記サンプルでmarginプロパティーは一切使っていません。

なお、この記事では要素間の空白部分のことをmarginの訳語としての「余白」との混乱を避けるため、「アキ」と呼ぶことにします。

gapプロパティーの使い方

gapプロパティーはFlexboxおよびCSS Gridで要素間のアキを設定できます。

.hoge {
  display: flex;
  gap: 30px 20px;
}

.fuga {
  display: grid;
  gap: 30px 20px;
}

gapプロパティーは1つもしくは2つの値をとり、それぞれ行間・列間のアキを設定します。1つ指定の場合は行間・列間を同一の値で指定します。2つの場合は、1つ目が行間、2つ目が列間の値になります。gapプロパティは一括指定プロパティーなので、行間・列間を個別に指定する場合はそれぞれrow-gapcolumn-gapを使います。

CSS Gridでのgapプロパティーの使い方などは『CSS Grid Layout入門』の記事に詳しく解説されています。

marginを使わないページデザイン

サンプルのページはデスクトップレイアウト、モバイルレイアウトともにmarginプロパティーを使っていません。gapプロパティーおよびpaddingプロパティーのみを用いて要素をレイアウトしています。いくつかmarginプロパティーを使わない手法をピックアップして解説します。

全体のレイアウトについて

まず全体のレイアウトについて見ていきます。大きく分けてヘッダー、メインコンテンツ、フッターとモバイル版のハンバーガーボタン(デスクトップ版では非表示)で構成されています。さらにメインコンテンツは7つセクションに分かれています。ヘッダー・コンテンツ・フッターはCSS Gridを使っています。(細かい内部のHTMLは省略しています)

<body class="global-layout">
  <header class="global-layout__header header">...</header>
  <button class="global-layout__hamburger-button hamburger-button">
    ...
  </button>
  <main class="global-layout__contents contents-layout">...</main>
  <footer class="global-layout__footer footer">...</footer>
</body>
.global-layout {
  display: grid;
  grid-template: "header" auto "contents" 1fr "footer" auto/100%;
  min-height: 100vh;
}

.global-layout__header {
  position: fixed;
  top: 0;
  left: 0;
  grid-area: header;
  width: 100%;
}

.global-layout__contents {
  grid-area: contents;
}

.global-layout__footer {
  grid-area: footer;
}

縦に並んだレイアウトでもCSS Gridで構成するメリットととして、コンテンツの高さが小さい時にフッターが上がってきてしまう現象を防げる点です。CSS Gridでmin-height: 100vhで最小高さとして画面の高さを設定し、コンテンツ部分の高さを1frと設定することで100vhからヘッダーとフッターの高さを引いた残りがコンテンツ部分の高さとして確保されます。これによりフッターは最低でも画面の一番下に配置されます。細かいCSS Gridやfrのしくみについては『CSS Grid Layout入門』をご参照ください。

メインコンテンツは7つのセクションが縦に並んで、各セクションは120pxずつ離れています。これはFlexboxとgapを組み合わせて次のように実装できます。

<main class="global-layout__contents contents-layout">
  <section class="main-visual">...</section>
  <section class="missions">...</section>
  <section class="service">...</section>
  <section class="news">...</section>
  <section class="works">...</section>
  <section class="company">...</section>
  <section class="credits">...</section>
</main>
.contents-layout {
  display: flex;
  flex-direction: column;
  row-gap: 120px;
  padding-bottom: 120px;
}

display:flexflex-direction:columnで縦に並べています。Flexboxといえば横並びのイメージが強いですが、flex-directionプロパティーを用いれば縦方向へのレイアウトも可能です。これでFlexboxによるgapプロパティーが使えるようになり、row-gap:120pxを設定すると各セクション間に120pxずつのアキができます。gapプロパティーを使うと個々にmargin-bottommargin-topを設定しなくても一括で設定でき便利です。

ヘッダー

デスクトップ版のヘッダーは大きく分けてロゴ部分とナビゲーション部分に分けられます。それぞれ両端に寄っているので次のようなHTMLとCSSで配置します。

<header class="global-layout__header header">
  <h1>HOGE</h1>
  <nav>
    <ul class="header__navigation">
      <li>
        <a href="#">ABOUT</a>
      </li>
      <li>
        <a href="#">WORKS</a>
      </li>
      <li>
        <a href="#">COMPANY</a>
      </li>
      <li>
        <a href="#">CONTACT</a>
      </li>
    </ul>
  </nav>
</header>
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 32px;
}

つづいて、ナビゲーション部分に注目してみます。ナビゲーションはデスクトップ版では横並びになっています。ここでもFlexboxを用いて並べています。

.header__navigation {
  display: flex;
  column-gap: 32px;
  align-items: center;
  justify-content: flex-start;
  list-style: none;
}

ここでgapプロパティーの登場です。横並びの要素間のアキはcolumn-gapプロパティーによって実現しています。

従来であれば<li>要素に対してmargin-leftプロパティーと1つ目の要素に余計なmargin-leftプロパティーがつかないよう:first-child擬似クラスを用いていたかと思います。

▼従来的な方法

.header__navigation li {
  margin-left: 32px;
}

.header__navigation li:first-child {
  margin-left: 0;
}

Flexboxとgapプロパティーを組み合わせればたったcolumn-gap: 32pxの1行で済みます。

Service部分

Serviceのセクションでは画像とテキストが横並びになっています。ここはそれぞれ幅50%の要素をdisplay: flexで並べているだけなのでそこまで真新しい手法は使っていません。なお偶数行のみflex-directionプロパティーで反転させています。

.alternating-layout__item {
  display: flex;
}

.alternating-layout__item:nth-child(2n) {
  flex-direction: row-reverse;
}

テキスト部分は上下左右中央に寄せていますが、これは次の2行でできます。

.alternating-layout__description {
  display: grid;
  place-items: center;
}

同じことはdisplay:flexjustify-content:centeralign-items:centerでもできますが、1行少なくかけるのでちょっとだけ省力化できます。

Works部分

Works部分は3つカードを並べるレイアウトとカード内部のレイアウトにdisplay:flexを使用しています。まずは3つ並べるコードをみていきます。

.card-list {
  display: flex;
  gap: 32px 24px;
  justify-content: center;
}

さきほどのナビゲーション部分と同様にカード間のアキはgapプロパティーを使って実現しています。今回はgapプロパティーに2つ値を与えて、row-gapプロパティーも設定しています。現状横に3つ並ぶ場合にはrow-gapプロパティーの影響はありませんが、将来的に2段になった場合に、上の段とアキができるよう設定しています。

カード自体のレイアウトは次のようなコードになっています。

.card {
  display: flex;
  flex-direction: column;
  row-gap: 8px;
}

Flexboxの向きを縦方向にし、画像とタイトル、タイトルと説明文の間をrow-gapプロパティーで空けています。縦方向のdisplay:flexを用いると行数の変化などにも柔軟なレイアウトが可能になります。

モバイル版について

モバイル版のレイアウトは全般的に横並びだったものを縦並びにしています。Flexboxを用いているので縦並びにしたい場合はflex-directionプロパティーの値をcolumnに変更することで対応できます。アキもrow-gapcolumn-gapプロパティー(あるいはgapプロパティー)の値を調整します。一括指定のgapプロパティーを用いれば、モバイル版ではPC版のmargin-leftプロパティーをキャンセルして新たにmargin-topプロパティーを与えるような書き方をしなくて済むのでシンプルになります。

gapの利点

gapプロパティーを使った実践的なレイアウトを紹介しましたが、gapプロパティーで要素間のアキをつくるメリットをあげます。

親が子要素間の余白を制御できる

marginプロパティーによるアキの制御と比べて大きな違いは子要素間のアキの制御を、Flexboxなどを使ってレイアウトを行っている親自身が制御できる点です。配置された子要素側はmarginなどの他要素のレイアウトに影響を与えるプロパティーを持つ必要がなく、自身のレイアウトに専念すれば良くなります。

このメリットはコンポーネント指向なCSS設計やフレームワークにおいて非常にメリットになります。「コンポーネントにmarginを持たせるな」というような言説を聞いたことないでしょうか?marginプロパティーは他の要素へ影響を与えるので意図しないレイアウトの崩れやCSSが複雑になる原因になります。gapプロパティーによるアキの制御はこれを実現できます。

本記事のデモでもレイアウトに関わるCSSは階層状に制御しています。

Adobe XDやFigmaといったデザインツールの親和性が高い

Adobe XDには「スタック」、Figmaには「Auto layout」という機能があります。この挙動はFlexboxとgapプロパティーを組み合わせたもの近いです。デザインデータと同じような感覚でレイアウトができ、デザイナーの意図も汲み取りやすいでしょう。

リスト項目の:last-childのような例外処理を省ける

ナビゲーションのレイアウト部分でも取り上げましたが、marginプロパティーを使った場合は不要なアキが発生する場合があります。それを回避するために:last-child:nth-childなどの擬似クラスを使った例外処理をすることもありますが、gapプロパティーが要素と要素の間を制御するのでそのような記述をせずに済みます。

marginの相殺など考えなくてよい

marginプロパティー同士が隣接した場合「marginの相殺」という挙動があります。さきほどのコンポーネントの話と関連しますが、意図しないmarginの相殺や逆に二重にマージンがついてしまうといった問題を考えなくて済みます。

gapの欠点

しかしgapプロパティーにもデメリットがあります。

対応していないブラウザを利用している人も多い

2021年6月現在、Can I Use… の統計によるとサポート率は全体で約75%です。IEが対応していないのは仕方ないですが、モバイルで多くのシェアを占めるSafari on iOSも14.6以降の対応なのでまだ利用できないユーザーが多いです。これに関してはブラウザのアップデートとともに解消されるでしょう。

細かいアキの制御が難しい

gapプロパティーはすべてのアキを1つのプロパティーで設定するので各要素間のアキを細かくするのは難しいです。その場合最低限のアキをgapプロパティーで制御しつつ、余分なものは別途marginプロパティーやpaddingプロパティーで調整する必要があるでしょう。

ラッピング要素が多くなるかもしれない

要素同士をレイアウト決定するために親要素を必要になるので、単純に並べた場合に比べ<div>要素などが多くなるかもしれません。HTMLの階層が深くなりがちなので、開発体験に影響があるかもしれません。

まとめ

Flexbox・CSS Gridとgapプロパティーを用いたレイアウト手法を紹介しました。デモではmarginプロパティーを一切使わないという野心的なアプローチを取りましたが、実際にはmarginプロパティーを使わざるを得ない場面もあるでしょう。

しかし、gapプロパティーを活用しmarginプロパティーへの依存を減らすことで柔軟なレイアウトを実現できます。これからはmarginプロパティーと並んでgapプロパティーもレイアウトで使われるメジャーなCSSプロパティーとなるのではないでしょうか。