FlexBox または CSS Grid で作る header/footer/sidebar のスクロールバーレイアウト

f:id:Naotsugu:20211214211857p:plain


はじめに

ヘッダーがあり、Sticky フッダー があり、サイドメニューがあり、コンテンツが個別にスクロールする、よくあるレイアウトの FlexBox での作り方を、毎度分からなくなるので、メモしておきます。


FlexBox の基本

Flexible Box Module は行(row)方向、または列(column)方向の1次元のレイアウトモデルを提供し、アイテム間の余白や位置合わせの機能を提供します。

FlexBox を使うには、フレックスコンテナを用意する必要があります。コンテナとなる親要素に display: flex を指定することでフレックスコンテナとなります。

<div style="display: flex;">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
</div>

フレックスコンテナは、デフォルトで子要素が横方向に並びます。

入りきらない場合、flex-wrap: wrap を指定することで下段に回り込む形でレイアウトすることもできます(デフォルトは flex-wrap: nowrap )。

f:id:Naotsugu:20211214205959p:plain

フレックスコンテナに flex-direction: column; を指定すると、子要素は縦方向に並びます。

<div style="display: flex; flex-direction: column;">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
</div>

f:id:Naotsugu:20211214210238p:plain


Flex コンテナの子要素に flex-grow を指定すると、余ったスペースの伸長率を指定できます。

逆に flex-shrink は、スペースに余裕がない場合の伸縮率を指定します。

以下のように中央要素に flex-grow: 1; とした場合、他の伸長率がデフォルト 0 であるため、スペースに余りが有る場合、中央の要素が横に伸長します。

<div style="display: flex;">
  <div>Item 1</div>
  <div style="flex-grow: 1;">Item 2</div>
  <div>Item 3</div>
</div>

以下のようになります。

f:id:Naotsugu:20211214210532p:plain

フレックスコンテナが flex-direction: column; だった場合には、縦方向に伸長します。


ベースレイアウト

ここまでで見てきた FlexBox を使ってレイアウトを作成していきます。 ここでは、2カラムのレイアウトを FlexBox で定義します。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
  </head>

  <body style="margin:0; display: flex; height: 100vh; width: 100vw">

    <dev id="left" style="flex-shrink: 0; width: 12em;">
    </dev>

    <dev id="right" style="flex-grow: 1; background: #F5F5F5;">
    </dev>

  </body>
</html>
  • body を Flex コンテナにするため display: flex; を追加
  • bodyheight: 100vh; width: 100vw を指定してビューポートのサイズとする
  • 左側は固定サイズとするため flex-shrink: 0; width: 15em; を指定
  • 右側は画面いっぱいに広げるため flex-grow: 1; を指定

この時点で以下のようなレイアウトになります。

f:id:Naotsugu:20211214210757p:plain


左ボックスの作成

左側のボックスを、上部をヘッダ、下部をメニューとするため、FlexBox をネストして定義します。

<dev id="left" style="flex-shrink: 0; width: 12em; display: flex; flex-direction: column;">
  <header style="height: 3em; flex-shrink: 0; background: #333333; ">
  </header>
  <aside style="flex-grow: 1; overflow-y: auto;">
  </aside>
</dev>
  • display: flex; flex-direction: column; として子要素を縦に配置
  • ヘッダ部分は height: 3em; flex-shrink: 0; として縦幅を固定
  • サイドメニュー部分(adide) は flex-grow: 1; overflow-y: auto; として下まで伸長させ、はみ出る場合はスクロールバーを表示

この時点で以下のようなレイアウトになります。

f:id:Naotsugu:20211214210901p:plain


右ボックスの作成

右側のボックスも同様にFlexBox で定義します。

ヘッダ・メインを縦に並べ、メインはスクロールバーでスクロール可能とします。さらに、メインはFlexBoxをネストさせ、article と footer を、こちらも縦に並べます。

<dev id="right" style="flex-grow: 1; display: flex; flex-direction: column;">

  <header style="height: 3em; background: #333333; flex-shrink: 0;">
  </header>

  <main style="flex-grow: 1; overflow-y: auto; display: flex; flex-direction: column;">

    <article style="flex-grow: 1; background: #F5F5F5;">
      <h3>Article</h3>
    </article>

    <footer style="height: 3em; flex-shrink: 0;">
      <span>footer</span>
    </footer>

  </main>

</dev>
  • display: flex; flex-direction: column; として子要素を縦に配置
  • ヘッダ部分は左パネルと同様
  • メイン部分(main) は flex-grow: 1; overflow-y: auto; として下まで伸長させ、はみ出る場合はスクロールバーを表示
  • メイン部分はネストして FlexBox コンテナとし display: flex; flex-direction: column; を指定
  • articleflex-grow: 1; として余白を下まで伸長
  • footerheight: 3em; flex-shrink: 0; としてサイズを固定

以下のようなレイアウトになります。

f:id:Naotsugu:20211214211210p:plain

これでレイアウトは完成です。


CSS の抜き出し

style で定義した内容をスタイルシートに抜き出しておきましょう。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Title</title>
  </head>
  <body>

    <dev id="left">
      <header></header>
      <aside></aside>
    </dev>

    <dev id="right">
      <header></header>
      <main>
        <article>
          <h3>Article</h3>
        </article>
        <footer><span>footer</span></footer>
      </main>
    </dev>

  </body>
</html>

style.css

body {
  margin:0;
  height: 100vh;
  width: 100vw;
  display: flex;
}

#left {
  flex-shrink: 0;
  width: 12em;
  display: flex;
  flex-direction: column;
}

#right {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
}

header {
  height: 3em;
  background: #333333;
  flex-shrink: 0;
}

aside {
  flex-grow: 1;
  overflow-y: auto;
}

main {
  flex-grow: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

article {
  flex-grow: 1;
   background: #F5F5F5;
}

footer {
    height: 3em;
    flex-shrink: 0;
}


コンテンツの埋め込み

サンプルとしてコンテンツを埋め込んでみましょう。

Bootstrap と Bootstrap Icons を追加しておきます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
    <link href="style.css" rel="stylesheet">
    <title>Title</title>
  </head>
  <body>

    <dev id="left">

      <header>
        <a class="navbar-brand fs-2 text-light ms-3" href="#">Bland</a>
      </header>

      <aside style="flex-grow: 1; overflow-y: auto;">
        <div class="pt-3">
          <ul class="nav flex-column">
            <li class="nav-item">
              <a class="nav-link fw-bold active" aria-current="page" href="#">
                <i class="bi bi-house-door"></i> Home
              </a>
            </li>
            <li class="nav-item">
              <a class="nav-link fw-bold" href="#">
                <i class="bi bi-clipboard-plus"></i> Orders
              </a>
            </li>
            <!-- 省略 -->
          </ul>
        </div>
      </aside>
    </dev>

    <dev id="right">

      <header></header>

      <main>
        <article class="p-3">
          <h3>What is Lorem Ipsum?</h3>
          <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
          <!-- 省略 -->
        </article>
        <footer class="m-4"><span class="text-secondary">© 2021 Example, Inc.</span></footer>
      </main>
    </dev>

  </body>
</html>

表示は以下のようになります。

f:id:Naotsugu:20211214212151p:plain

サイドメニューとコンテンツ部分は、画面からはみ出る場合はスクロールバーとなります。

f:id:Naotsugu:20211214212225p:plain

フッターは Sticky Footer となり画面下部に固定されます。

f:id:Naotsugu:20211214212257p:plain


CSS グリッドレイアウト

ここまで FlexBox でレイアウトを作成しましたが、同じことを CSS Grid Layout で作成してみましょう。

FlexBox は1次元のレイアウトだったのに対して、CSS Grid Layout は 2次元のレイアウトとなります。

CSS Grid は display: grid でコンテナを作成し、コンテナのグリッド定義を grid-template-rowsgrid-template-columns で定義します。

例えば、2行3列のグリッドコンテナは以下のように定義します。

  <div style="display: grid;
              grid-template-rows: 100px 100px;
              grid-template-columns: 50px 50px 1fr">
</div>

fr はグリッドの空間の割当比率を指定する単位で、上記の例 1fr は、他が固定サイズであるため、3カラム目が残りの余白を埋めるように伸長します。ここでは述べませんが、auto であったり minmax(100px, 150px) であったり、いろいろなサイズの指定方法があります。

コンテナ直下の要素では、その要素にグリッドのどの領域を割り当てるかを指定します。 この指定は、グリッドの罫線の番号を指定することに注意してください。例えば、左上のセルを割り当てる場合は、grid-row: 1 / 2; grid-column: 1 / 2; という指定となります。1つ目の罫線から2つ目の罫線までを割り当てるという意味になります。

以下の例では、1 行 x 3列 を割り当てる例となります。grid-row: 1 / 2grid-row: 1 と省略して書くことができます。

<div style="grid-row: 1; grid-column: 1 / 4;"></div>


FlexBox で作成したものを CSS Grid で定義すれば以下のようになります。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
  </head>

  <body style="margin: 0; height: 100vh; width:  100vw;
               display: grid;
               grid-template-rows: 3em 1fr;
               grid-template-columns: 12em 1fr">

      <header style="grid-row: 1;
                     grid-column: 1 / 3;
                     color: white;
                     background: #333333;">
        header
      </header>

      <aside style="grid-row: 2;
                    grid-column: 1;
                    overflow-y: auto;">
        aside
      </aside>

      <main style="grid-row: 2;
                   grid-column: 2;
                   overflow-y: auto;
                   display: grid;
                   grid-template-rows: 1fr 3em;
                   grid-template-columns: 1fr">

        <article style="grid-row: 1;
                        grid-column: 1;
                        background: #F5F5F5;">
          atticle
        </article>

        <footer style="grid-row: 2;
                       grid-column: 1;">
          footer
        </footer>

      </main>

  </body>
</html>

スクロールバーの表示位置の関係で、main 内に CSS Grid Layout をネストして定義しています。

表示は以下のようになります。

f:id:Naotsugu:20211215212623p:plain

同じ様にコンテンツを埋め込めば以下のように、FlexBox のものと同様となります。

f:id:Naotsugu:20211215220625p:plain


まとめ

FlexBox を使い、ヘッダー、フッダー、サイドバー構成の簡単なレイアウト定義について紹介しました。 同様の構成を CSS Grid Layout で定義する方法についても確認しました。

いずれも同様のレイアウトが定義できますが、カラム数が固定されるようなケースでは、2次元レイアウトである CSS Grid によるレイアウトが見通しが良く簡単です。

一方、カードのような任意個数のコンテンツを並べるようなケースでは、FlexBox が適切です。

適材適所で使い分けていきたいですね。