🎁

position: fixed;を使わずにヘッダー/フッターを固定する方法【FlexBox解説】

10 min read

この記事ではCSSのFlexBoxという機能を使ってヘッダー/フッター固定のWebサイトを作る方法について紹介します。

ヘッダー/フッターを固定する方法をネットで調べてみると「position: fixed;に設定すれば良い」という記事が数多くヒットします。
この方法はヘッダー/フッターの高さが固定のときやデザイン的に敢えてメイン要素に被せたいというときは良いですが、そうではないときメイン要素がヘッダー/フッターに重なってしまって見えないという問題点があります。(詳細は後述)

そこでこの記事ではよくある"position: fixed;"ではなく"FlexBox"を使って、ヘッダー/フッターが可変長の場合でも対応できる固定方法について紹介します。

想定読者

これからHTML/CSSの勉強をしようとしている初心者の方。
1から詳しく解説しているのである程度HTML/CSSを書いたことがある方にとっては冗長かもしれません。
手っ取り早く作り方を知りたいという方は「完成形」や「FlexBoxを使おう」の章を見てください。

完成形

完成形は以下のようなものになります。
ちゃんとヘッダーとフッターが固定されていますね。これらがメインコンテンツと重なることもなく、メインコンテンツがはみ出るときはスクロールできるようになっています。また、画面の高さ全体ではなく、メインコンテンツの範囲内にスクロールバーが表示されています。

まずはHTMLを書く

まずはHTMLを書きます。

sample.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ヘッダー/フッターを固定する</title>
  </head>
  <body>
    <header>ヘッダー</header>
    <main>ここにメイン要素を書くよ</main>
    <footer>フッターだよ</footer>
  </body>
</html>

HTMLだけ書いた状態
このままでは上から3行並べただけの状態ですので、CSSを使って各要素の位置を調整していこうと思います。
ちなみに上記のコードでは<header>タグ、<main>タグ、<footer>タグを使っていますが
<div class="hoge"> ヘッダー</div>みたいにclassを使っていても問題ありません。
大事なのはCSSを書く際にヘッダー・メインコンテンツ・フッターの3要素を区別できるかということです。

下準備

CSSを適用するための設定

まずはCSSが適用されるようにHTMLファイルをCSSファイルの設定を足します。ここではsample.htmlと同じディレクトリにstylesheet.cssという名前のファイルがあるということにします。

sample.html
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="stylesheet.css" /> <!-- ←ここ! -->
    <title>ヘッダー/フッターを固定する</title>
  </head>

背景色・透明度の設定

次にヘッダー・メインコンテンツ・フッターの領域がはっきりわかるようCSSで各要素に背景色を設定しておきます。領域が重なっていないか確認するため、ヘッダーとフッターの透明度を50%にします。

CSSには透明度を設定するopacityというプロパティがあります。

<!--この方法だと文字の透明度も50%になる-->
header {
  background-color: rgba(255, 253, 237);
  opacity: 0.5;
}

みたいな感じで使うやつです。しかしopacityだと文字も一緒に透過してしまうため、「背景色の透明度だけを変えたい」という場合は以下のようにrgbaで色を設定します。

stylesheet.css
@charset "utf-8";

header {
  background-color: rgba(245, 178, 177, 0.5);
}

main {
  background-color: rgba(255, 253, 237, 1);
}

footer {
  background-color: rgba(201, 187, 155, 0.5);
}

rgbaの中には4つの数値が並んでいますが、最初の3つで色を設定し、最後の1つで透明度を設定します。透明度は0から1までの値を設定でき、0に近いほど透明になります。ここではヘッダーとフッターの透明度を50%(0.5) にしました。
各背景色を設定

marginの設定

上記の写真を見ると余白に関する設定をどこにも書いていないのにも関わらず謎の余白が存在することがわかります。
これはブラウザがデフォルトで<body>タグにmarginを設定しているからです。(ちなみに多くの場合8pxに設定されているらしい)
この余白を消すには<body>タグのスタイルにmargin:0px;と書いてあげます。

stylesheet.css
body {
  margin: 0px;
}

画面の余白を無くした
これで下準備は完了です!

position:fixed;を使ったよくある方法

FlexBoxの説明に入る前にまずはよくあるposition:fixed;を使った実装方法とその問題点について説明します。
完成形のコードはこちら↓

stylesheet.css
@charset "utf-8";

body {
  margin: 0px;
}

header {
  position: fixed;
  top: 0px;
  width: 100%;
  background-color: rgba(245, 178, 177, 0.5);
}

main {
  margin-top: 24px;
  background-color: rgba(255, 253, 237, 1);
}

footer {
  position: fixed;
  bottom: 0px;
  width: 100%;
  background-color: rgba(201, 187, 155, 0.5);
}

実装方法

初めにヘッダーを固定します。<header>タグにposition:fixed;と書きます。

header {
  position: fixed;
  background-color: rgba(245, 178, 177, 0.5);
}

position: fixed;を足しただけの状態
ヘッダーの横幅が文字の長さになってしまった上に、メインコンテンツと被ってしまっています。
そこで以下のように設定します。

header {
  position: fixed;
  width: 100vw;
  background-color: rgba(245, 178, 177, 0.5);
}

main {
  margin-top: 24px;
  background-color: rgba(255, 253, 237, 1);
}

横幅に関してはwidth: 100vw;としてます。これは画面横幅いっぱいの長さにするという意味です。

ちなみにwidth: 100%;と書くと「親要素の幅の100%の長さの幅にする」という意味になります。heightも同様です。
このコードでは<header>タグの親要素は<body>タグになっていますが、<body>タグのwidthheightは設定されていません。設定されていない場合widthheightautoになります。widthの場合は今回のケースだとたまたまうまくいくのですが、heightの場合はうまくいきません。もし%で幅や高さを指定したい場合は親要素の<body>やその親要素である<html>widthheightを設定してあげてください。[1]

メインコンテンツと被る問題に関しては<main>タグにmargin-top: 24px;を適用し、メインコンテンツの上部にヘッダーの分だけ余白をつけることで解決しました。
position: fiexd;でヘッダー固定

次にフッターも固定します。やり方はヘッダーと同様ですがヘッダーと全く同じにすると一番上に固定されてしまいます。position: fixed;では何も指定がない場合、画面の一番上かつ左端(つまり現在のヘッダーの位置)に固定されるからです。
画面下に固定するためには以下のように固定する位置を指定する必要があります。

footer {
  position: fixed;
  bottom: 0px;
  width: 100%;
  background-color: rgba(201, 187, 155, 0.5);
}

bottom: 0px;は画面の1番下に固定するという意味です。
同様にしてヘッダーにもtop: 0px;と書き、画面上に固定することを明示しても良いです。

問題点

上のcodepenを触ってもらうとわかると思うのですが、position: fixed;の問題点、それはヘッダーやフッターとメインコンテンツが被ってしまうことです。たまたまメインコンテンツが短くスクロールせずに済む場合や逆にめちゃくちゃ長い場合は(ヘッダー・フッターの透明度を0%にしたら)うまくいっているようにも見せれますが、今回のように中途半端な長さだと表示されない文章が出てきます。

また、ヘッダーとメインコンテンツの被りを「ヘッダーの高さの分だけメインコンテンツの上部に余白を設ける」ことによって解決していましたが、ヘッダーの高さがヘッダー内の文章量によって変わったり、スマホとPCで高さが違ったりなど、少しでも高さが変わった場合はうまくいかなくなるでしょう。

なぜposition:fixed;だとうまくいかないのか?

position: fixed;はペイントアプリでいう「レイヤー」とか写真でいう「フォトフレーム」みたいな感じだからです。つまり画面の上から重ねているからです。
具体的に言うと、画面いっぱいのメインコンテンツ領域があって、その上にヘッダーとフッターを重ねています。下の要素(メインコンテンツ)がどんなものであっても定位置に固定されますし、逆にヘッダーやフッターがあるからといってその分だけメインコンテンツの領域が縮んだりもしません。そのため、メインコンテンツと被ってしまう問題が発生してしまったということです。

となるとヘッダー/フッターを固定するのにposition:fixed;を使うのはあまり良くない気がしてきます。メインコンテンツの上から重ねるのではなく、画面の上と下にヘッダー/フッターを置き、余った領域がメインコンテンツになるよう設定する方が良さそうです。

メインコンテンツ領域を狭めたいという図
そこで登場するのがFlexBoxです!

FlexBoxを使おう

FlexBoxとは

FlexBoxとは"Flexible Box Layout Module"の略で、要素を縦方向あるいは横方向にレイアウトするための1次元のレイアウト方法です。
要素を横に並べたいときによく使われますが、今回のように「残りの領域を全部メインコンテンツ領域にしたい」といった場合にもとても便利です。

FlexBoxは1次元のレイアウト方式です。横方向か縦方向のレイアウトしか決めれません。横方向のレイアウトも縦方向のレイアウトも設定したいという場合はFlexBoxを入れ子にするか、2次元のレイアウト方式であるGridを使いましょう。

FlexBoxは コンテナ(親要素)アイテム(子要素) の2つの要素で構成されています。
コンテナというのはアイテムたちが配置される領域全体のことです。例えば唐突に「A要素を真ん中に置きたい!」と言われても画面全体から見て真ん中なのか特定の領域における真ん中なのか、どの範囲内での話なのかわからないわけです。この「どの範囲内なのか」というのがコンテナに当たります。
そしてアイテムは各要素のことを指します。
今回でいうとコンテナ→画面全体、アイテム→ヘッダー・フッター・メインコンテンツですね。

実装方法

それでは早速実装していきましょう。まず今回に限ってはコンテナ=画面全体であるため、コンテナ=<body>タグにしてしまってもいいのですが、汎用性を持たせるという意味で<body>内にcontainerクラスを作り、その中にアイテム(<header><main><footer>タグ)を配置することにします。

sample.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="stylesheet.css" />
    <title>ヘッダー/フッターを固定する</title>
  </head>
  <body>
    <div class="container">
      <header>ヘッダーだよ</header>
      <main>ここにメイン要素を書くよ</main>
      <footer>フッターだよ</footer>
    </div>
  </body>
</html>

次にcontainerクラスのスタイルを設定を書きます。
FlexBoxの機能を使うためにdisplay: flex;とします。
display: flex;を足しただけの状態
デフォルトだとアイテムが横並び(flex-direction: row;)になってしまうため、flex-direction: column;とし、縦方向のレイアウトであるということを設定します。
また今回はコンテナ領域=画面全体であるため、画面いっぱいの幅・高さであることを示すwidth: 100vw;height: 100vh;を指定します。

sytlesheet.css
.container {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
}

FlexBoxの方向を修正
次にメインコンテンツのスタイルを変更します。といってもflex:1を加えるだけです。

stylesheet.css
main {
  flex: 1;
  background-color: rgba(255, 253, 237, 1);
}

たったこれだけで完成です!!!
完成形

デフォルトの設定だとブラウザによっては画面外にはみ出てる要素がなくても無理矢理スクロールできて余白が見えてしまったりするのでそれが嫌な方は

body {
  margin: 0px;
  overflow: hidden;
}

としましょう。

flex: 1;にするとなぜうまくいくのか?

mainflex: 1;と書くだけでヘッダー・フッターと重ならず、なおかつヘッダー・フッター以外の領域が全てメインコンテンツの領域になりました。このflex: 1;は一体何を設定しているのでしょうか?

実はflexというのはflex-growflex-shrinkflex-basisをまとめて指定するショートハンドで、flex: 1;flex-grow: 1;flex-shrink: 1;flex-basis: 0;という意味です。
flex-shrinkflex-basisは今回のケースではさほど重要ではないため、flex-growについて説明します。
flex-shrinkflex-basisについても知りたいという方にはこちらの記事が分かりやすくてオススメです。[2]

flex-grow とは

コンテナの面積 > アイテムの面積の合計の場合、コンテナ内に全てのアイテムを並べても余る領域が出てきます。そんなとき、余った領域をどうするか?というのがflex-growです。
flex-growの図解
flex-growは各アイテムごとに設定します。flex-growは0以上の整数であり、何も指定がない場合0になります。これは何もしない、つまり余った領域をそのままにしますという意味です。余った領域を0:0:0の割合で割り振ると捉えることもできるでしょう。
例えば<header>タグのflex-growが1、<main>タグが2、<footer>タグが3のときは、余った領域を1:2:3の割合で割り振るという意味になります。言い換えると、余った領域の高さ(flex-direction: rowなら横幅)の1/(1+2+3)=1/6だけヘッダーを伸ばし、2/(1+2+3)=2/6だけメインコンテンツを伸ばし、3/(1+2+3)=3/6だけフッターを伸ばすという意味になります。
今回はflex-growの値がヘッダー=0、メインコンテンツ=1、フッター=0なので、余った領域を0:1:0の割合で割り振るという意味になります。つまり、余った領域を全てメインコンテンツにしますということです。

このようにflex-growは(コンテナ内に全アイテムを並べてもスペースが余る場合)各アイテムの伸びる倍率を設定するものです。

今回のように「画面の上と下にヘッダー/フッターを置いた後、余った領域がメインコンテンツになるように設定したい」というときにまさにうってつけの機能だというわけです!

おわりに

この記事ではposition: fixed;の問題点を述べたあと、FlexBoxを使ったヘッダー/フッターを固定する方法について解説しました。
FlexBoxの使い方を覚えればヘッダー/フッターを固定するのはもちろん、横に要素を並べたり、要素を真ん中に固定したりなど、柔軟なレイアウトができるようになります。それにFlexBoxが理解できていれば2次元のレイアウトであるGridもすぐに理解できることでしょう。
ぜひこれを機にFlexBoxを使ってみてください!

脚注
  1. https://saruwakakun.com/html-css/basic/width-height ↩︎
  2. https://web.hazu.jp/css-flexbox-03/ ↩︎

Discussion

ログインするとコメントできます