🍱

CSS Grid Layout を使って複雑なレイアウトを綺麗に書きたい

2022/09/13に公開

以下のようなレイアウトを持つ画面を作成しようとした場合、アイテムの配置に CSS Flexbox を利用するよりも、CSS Grid Layout を利用する方が作りやすいと思います

また、以下のように条件によって異なるレイアウトを求められる場合、CSS in JS のフレームワークを用いれば、綺麗に書けます

今回使っている技術は以下です

  • React v16.12.0
  • Emotion v10.0.27

実装

コード全体は以下です

import React from "react";
import { css } from "@emotion/react";
import styled from "@emotion/styled";

const borderStyle = css`
  border: 0.5px solid #ddd;
`;
const ContainerStyle = styled.div<{ type: "A" | "B" }>`
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas: ${(p) =>
    p.type === "A"
      ? `'title title title'
    'item00 item01 item01'
    'item10 item11 item12'
    'footer footer footer'`
      : `'title title title'
    'item00 item01 item01'
    'item10 item10 item10'
    'footer footer footer'`};
  text-align: center;
  ${borderStyle}
`;
const TitleStyle = styled.h3`
  grid-area: title;
  ${borderStyle}
`;
const ItemStyle = styled.div<{ i: number; j: number }>`
  grid-area: ${(p) => `item${p.i}${p.j}`};
  ${borderStyle};
`;
const FooterStyle = styled.div`
  grid-area: footer;
  ${borderStyle};
`;

export const Component: React.FC<Props> = ({ type, items }) => (
  <ContainerStyle type={type}>
    <TitleStyle>
      <div>タイトル</div>
    </TitleStyle>
    {items.map((gs, i) =>
      gs.map((s, j) => (
        <ItemStyle key={s.id} i={i} j={j}>
          <div>{s.name}</div>
        </ItemStyle>
      ))
    )}
    <FooterStyle>
      <div>フッター</div>
    </FooterStyle>
  </ContainerStyle>
);

それぞれ説明します

全体的なスタイル

grid-template-areasを利用し、名前付きのグリッドテンプレートを定義します
Emotion を使って、パラメーターtypeを受け取り、利用するテンプレートを決定します

const ContainerStyle = styled.div<{ type: "A" | "B" }>`
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas: ${(p) =>
    p.type === "A"
      ? `'title title title'
    'item00 item01 item01'
    'item10 item11 item12'
    'footer footer footer'`
      : `'title title title'
    'item00 item01 item01'
    'item10 item10 item10'
    'footer footer footer'`};
  text-align: center;
  ${borderStyle}
`;

レイアウトの追加があっても、テンプレートと条件を追加で対応できるし、レイアウトの変更もテンプレートを直すだけでおそらく対応可能です
ただし、このコンポーネントを一般化する時には、テンプレート定義は切り離して props 経由で渡される方がいいかもしれません

各要素のスタイル

grid-areaでテンプレートと紐付けます
ItemStyle は値が複数あり、ループを使って書きたいので map のインデックスを名前として使います

const TitleStyle = styled.h3`
  grid-area: title;
  ${borderStyle}
`;
const ItemStyle = styled.div<{ i: number; j: number }>`
  grid-area: ${(p) => `item${p.i}${p.j}`};
  ${borderStyle};
`;
const FooterStyle = styled.div`
  grid-area: footer;
  ${borderStyle};
`;

Grid Layout を利用するケースってあまり今までなかった(Flexbox で全部やってた)のですが、調べると色々な機能があって便利だなと思いました

Discussion