ドロワーメニュー(ハンバーガーメニュー)をCSSだけで作る方法[css]

ドロワーメニュー(ハンバーガーメニュー)をCSSだけで作る方法
今後、定期的に「基礎的なUIパーツのつくりかた」の記事を書いていこうと思っています。

まず最初にお届けするのは、Webサイトのヘッダーでよく見かける、3本線のあのUIパーツについてです。
そう、「ハンバーガーメニュー」の事です。
海外を中心に、このメニューはドロワーメニュー(マテリアルデザインでは Navigation Drawer )とも呼ばれています。

ユーザーが手にするデバイスが多様化した現代のWeb制作において、モニタ幅が小さい端末に、項目の多いメニューを効率よく配置するのに定番のUIパーツですね。

今回は、そんな定番のUIパーツであるドロワーメニュー(ハンバーガーメニュー)をcssだけで作る方法についての記事になります。

早速コードをご紹介。htmlについて

まずはhtml。最低限必要なhtmlとしては、次の通りとなります。
見てわかる通り、html側は比較的少ない記述で済みます。

<header class="header-container">
  <div class="header-body">
    <!-- ドロワーメニュー部分 -->
    <nav class="header-navigation">
      <label for="drawerCheckbox"></label>
      <input class="header-navigation-check" id="drawerCheckbox" type="checkbox" />

      <!-- 3本線のアイコン -->
      <span class="header-navigation-icon-container">
        <span class="header-navigation-icon"></span>
      </span>

      <!-- ドロワー本体 -->
      <div class="drawer-container">
        <h2 class="drawer-title">Categories</h2>
        <ul class="drawer-list">
          <li>menu01</li>
          <li>menu02</li>
          <li>menu03</li>
          <li>menu04</li>
          <li>menu05</li>
        </ul>
      </div>

      <!-- ドロワーの半透明背景 -->
      <div class="drawer-background"></div>
    </nav>
    <!-- // ドロワーメニュー部分 -->
  </div>
</header>

ポイントとなるのは6行目のチェックボックス( input type=”checkbox” ) と、
その同じ階層にドロワーメニュー本体、トリガーとなる3本線のマークが並列で存在していると言う点です。

同じ階層に並列で存在している、と言うことは
共通の親要素を直前に持っている」と言うことになります。

詳しくは続くcssの部分で書きますが、cssのみでドロワーメニューを作る場合、これがとても重要なポイントになります。

cssについて

さて、本題となるcssは、次にご紹介するコードの通りとなります。
(やや長いので[css全体像のコードを開く]というアコーディオン内に記載しています)

cssのみでドロワーメニューを作る」のがテーマなので、記述量はそれなりにありますが、
大きく分けて次の項目についての記述となります。

[1]. ヘッダーの定義
[2]. ドロワーメニュー全体の親要素
[3]. ドロワーメニュー本体
[4]. 3本線アイコンの親要素
[5]. 3本線アイコン_真ん中の線のみタグとして存在、上下は::before, ::after擬似要素
[6]. チェックボックス_これが :checked かどうかが起点となる
[7]. チェックボックスが :checked になったことにより連動する各パーツ

それでは実際のcssコードに続いて、各[1]〜[7]について説明します。

css全体像のコードを開く
/* [1]. ヘッダーの定義 */
.header-container {
  height: 50px;
  width: 100%;
}
.header-body {
  background: #fff;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3);
  height: 50px;
  position: relative;
  text-align: center;
  width: 100%;
}
@media screen and (min-width: 1160px) {
  .header-inner {
    margin: 0 auto;
    position: relative;
    width: 1080px;
  }
}
/* // ヘッダーの定義 */

/* [2]. ドロワーメニュー全体の親要素 */
.header-navigation {
  height: 48px;
  position: absolute;
  right: 0;
  width: 56px;
  z-index: 70;
}
/* // ドロワーメニュー全体の親要素 */

/* [3]. ドロワーメニュー本体 */
.drawer-container {
  background: #fff;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3);
  height: 100vh;
  padding: 3.5rem 2rem 2rem;
  position: fixed;
  right: -300px;
  top: 0;
  transition: right 0.3s;
  width: 200px;
  z-index: 50;
}
.drawer-list {
  list-style-type: none;
  text-align: left;
}
.drawer-title {
  font-family: "Montserrat";
  font-size: 1rem;
  font-weight: 700;
  margin: 0 0 1rem;
  text-align: left;
}
.drawer-background {
  background: rgba(0, 0, 0, 0.5);
  display: none;
  height: 0;
  top: 0;
  width: 0;
  z-index: 10;
}
/* // ドロワーメニュー本体 */

/* [4]. 3本線アイコンの親要素 */
.header-navigation-icon-container {
  display: block;
  height: 21px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 21px;
}
/* // 3本線アイコンの親要素 */

/* [5]. 3本線アイコン_真ん中の線のみタグとして存在、上下は::before, ::after擬似要素 */
.header-navigation-icon {
  backface-visibility: hidden;
  background-color: #323232;
  border-radius: 2px;
  height: 2px;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  transform-origin: 0 25%;
  transition: transform 0.3s, background-color 0.3s;
  width: 21px;
  will-change: transform;
  -webkit-backface-visibility: hidden;
  z-index: 20;
}
.header-navigation-icon::before {
  background-color: #323232;
  backface-visibility: hidden;
  border-radius: 2px;
  content: "";
  display: inline-block;
  height: 2px;
  left: 50%;
  position: absolute;
  top: -8px;
  transform: translate(-50%, -50%);
  transform-origin: 0 25%;
  transition: transform 0.3s, background-color 0.3s;
  width: 21px;
  will-change: transform;
  -webkit-backface-visibility: hidden;
}
.header-navigation-icon::after {
  background-color: #323232;
  backface-visibility: hidden;
  border-radius: 2px;
  content: "";
  display: inline-block;
  height: 2px;
  left: 50%;
  position: absolute;
  top: 10px;
  transform: translate(-50%, -50%);
  transform-origin: 0 25%;
  transition: transform 0.3s, background-color 0.3s;
  width: 21px;
  will-change: transform;
  -webkit-backface-visibility: hidden;
}
/* // 3本線アイコン_中央の線のみタグとして存在、上下は::before, ::after */

/* [6]. チェックボックス_これが :checked かどうかが起点となる */
.header-navigation-check {
  cursor: pointer;
  display: block;
  height: 48px;
  margin: 0;
  opacity: 0;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  z-index: 110;
}
/* // チェックボックス_これが :checked かどうかが起点となる */

/* [7]. チェックボックスが :checked になったことにより連動する各パーツ */
.header-navigation-check:checked ~ .header-navigation-icon-container {
  left: initial;
  position: fixed;
  right: 7px;
  top: 24px;
  z-index: 100;
}
.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon::before {
  opacity: 1;
  top: 50.125%;
  transform: rotate(45deg) translate(-50%, -50%);
  visibility: visible;
}
.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon {
  background: rgba(50, 50, 50, 0);
  visibility: hidden;
}
.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon::after {
  opacity: 1;
  top: 50.125%;
  transform: rotate(-45deg) translate(-50%, -50%);
  visibility: visible;
}
.header-navigation-check:checked ~ .drawer-container {
  right: 0;
}
.header-navigation-check:checked ~ .drawer-background {
  display: block;
  height: 100vh;
  left: 0;
  position: fixed;
  width: 100vw;
  z-index: 5;
}
/* // チェックボックスが :checked になったことにより連動する各パーツ */

 

cssのそれぞれの項目について解説

前述の通り、上記のcssは次の7つの項目に分類できます。

[1]. ヘッダーの定義
[2]. ドロワーメニュー全体の親要素
[3]. ドロワーメニュー本体
[4]. 3本線アイコンの親要素
[5]. 3本線アイコン_真ん中の線のみタグとして存在、上下は::before, ::after擬似要素
[6]. チェックボックス_これが :checked かどうかが起点となる
[7]. チェックボックスが :checked になったことにより連動する各パーツ

一見複雑に見えるかもしれませんが、
個々に分けて見ていくとそれほど難しくはありませんので、
[1]〜[7]がそれぞれどんな役割をしているか、説明していきたいと思います。

[1]. ヘッダーの定義

この部分は、単純にドロワーメニューとそのトリガーとなる3本線アイコンを配置するための、
ベースのヘッダーについての記述になります。
見た目は必ずしもこの様にする必要はなく、一例として記載しています。

/* [1]. ヘッダーの定義 */
.header-container {
  height: 50px;
  width: 100%;
}
.header-body {
  background: #fff;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3);
  height: 50px;
  position: relative;
  text-align: center;
  width: 100%;
}
@media screen and (min-width: 1160px) {
  .header-inner {
    margin: 0 auto;
    position: relative;
    width: 1080px;
  }
}
/* // ヘッダーの定義 */

これで出来上がるのは、次のキャプチャの様な状態になります。
よく見かける、 box-shadow を持ったシンプルなヘッダーですね。

シンプルなヘッダー

[2]. ドロワーメニュー全体の親要素

こちらも至ってシンプルで、

・ドロワーメニュー本体
・トリガーとなる3本線アイコン
・ドロワーメニューを開閉する挙動の肝となるチェックボックス

を子要素に持つ、共通の親要素について書いている記述になります。

/* [2]. ドロワーメニュー全体の親要素 */
.header-navigation {
  height: 48px;
  position: absolute;
  right: 0;
  width: 56px;
  z-index: 70;
}
/* // ドロワーメニュー全体の親要素 */

[3]. ドロワーメニュー本体

これが、

「3本線のアイコンを押したら」
「スライドインしてくるドロワーメニュー」

の本体部分です。

.drawer-container が、
スライドインしてくるドロワーメニューの土台部分になります。
特徴としては、

・画面全体を覆う様に出現して欲しいので指定してある
position: fixed;

・縦幅がどんなモニタであろうと画面maxまで伸びて欲しいので指定してある
height: 100vh;

・開くまでは画面外にいて欲しいので指定してある
right: -300px;

・それから、スライドインしてくる時にアニメーションをつけたいので指定してある
transition: right 0.3s;

あたりでしょうか。

.drawer-list と .drawer-title がドロワー内部のコンテンツです。
これはお好みで自由にスタイリングして大丈夫な部分です。

そして
.drawer-background
↑こちらは、ドロワーメニューの土台部分である .drawer-container の下に
全画面で敷く半透明のグレー背景になります。

.drawer-container が上に載り
.drawer-background を下に敷きたい

↑こういうレイヤー関係にしたいので、
z-index の値を .drawer-container の方が大きくなる様に指定します。

ひとまずこの記述で、ドロワーメニュー本体を形づくる事ができます。

/* [3]. ドロワーメニュー本体 */
.drawer-container {
  background: #fff;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3);
  height: 100vh;
  padding: 3.5rem 2rem 2rem;
  position: fixed;
  right: -300px;
  top: 0;
  transition: right 0.3s;
  width: 200px;
  z-index: 50;
}
.drawer-list {
  list-style-type: none;
  text-align: left;
}
.drawer-title {
  font-family: "Montserrat";
  font-size: 1rem;
  font-weight: 700;
  margin: 0 0 1rem;
  text-align: left;
}
.drawer-background {
  background: rgba(0, 0, 0, 0.5);
  display: none;
  height: 0;
  top: 0;
  width: 0;
  z-index: 10;
}
/* // ドロワーメニュー本体 */

[4]. 3本線アイコンの親要素

続いて、3本線アイコンについてです。
この部分は3本線アイコンをラップ(包括)するための親要素になります。

このサンプルではヘッダ内に他の要素がないため position: absolute; を使用していますが、
デザインやヘッダ内の配置によっては position: relative; が使用されるケースも想定されます。

ここは特に全く難しい事なく、この位置に親要素を配置してくださいね、と言う様なことを書いている記述です。

しいてポイントを挙げるとすれば、position: absolute; をつかった時の要素のセンタリングとして、
left: 50%;
top: 50%;

を用いてまず、「要素の左上」を置きたい座標の中央に置いた後、
transform: translate(-50%, -50%);
を指定する事で、

「要素の左上」が置きたい座標にある状態から、
「要素の中央」が置きたい座標となるようにしている部分になります。

/* [4]. 3本線アイコンの親要素 */
.header-navigation-icon-container {
  display: block;
  height: 21px;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 21px;
}
/* // 3本線アイコンの親要素 */

[5]. 3本線アイコン_真ん中の線のみタグとして存在、上下は::before, ::after擬似要素

続いて、3本線アイコンについての記述です。
コメントアウトに記載の通り、あの3本線アイコンは
実体としては「spanタグ1個のみ」でつくる事が可能です。(他の2本の線は擬似要素
具体的には
.header-navigation-icon が、実体として存在しているspanタグ
.header-navigation-icon::before が上の線
.header-navigation-icon::after が下の線

となります。

3本の線それぞれ、クリックされたら上下の線が回転して
X型のcloseアイコンに変化
できる様、
transition: transform 0.3s, background-color 0.3s;
として、
transition を使用してアニメーションのための設定を定義しておきます。
( transition: all 0.3s; の様にしても動きますが、アニメーションさせる対象を細かく指定する事で、多少のパフォーマンス改善を期待しています )

/* [5]. 3本線アイコン_真ん中の線のみタグとして存在、上下は::before, ::after擬似要素 */
.header-navigation-icon {
  background-color: #323232;
  border-radius: 2px;
  height: 2px;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  transform-origin: 0 25%;
  transition: transform 0.3s, background-color 0.3s;
  width: 21px;
  z-index: 20;
}
.header-navigation-icon::before {
  background-color: #323232;
  border-radius: 2px;
  content: "";
  display: inline-block;
  height: 2px;
  left: 50%;
  position: absolute;
  top: -8px;
  transform: translate(-50%, -50%);
  transform-origin: 0 25%;
  transition: transform 0.3s, background-color 0.3s;
  width: 21px;
}
.header-navigation-icon::after {
  background-color: #323232;
  border-radius: 2px;
  content: "";
  display: inline-block;
  height: 2px;
  left: 50%;
  position: absolute;
  top: 10px;
  transform: translate(-50%, -50%);
  transform-origin: 0 25%;
  transition: transform 0.3s, background-color 0.3s;
  width: 21px;
}
/* // 3本線アイコン_中央の線のみタグとして存在、上下は::before, ::after */

↑この[4]〜[5]の部分で、次のキャプチャの様にヘッダー内に
3本線アイコンが配置できる
わけですね。

シンプルなヘッダーと3本線のアイコン

[6]. チェックボックス_これが :checked かどうかが起点となる

続いて、チェックボックス部分です。
このチェックボックスがとても重要です。

このチェックボックスがチェックされているか(:checked かどうか)が、
ドロワーメニューの開閉挙動を決める重要な点となるためです。

実際の開閉については、次の [7]. で説明しますが、まずはここでは、ガワの部分のcssです。
height, width で指定した値が
ヒット領域(クリック/タップに反応する領域)の大きさになります。

/* [6]. チェックボックス_これが :checked かどうかが起点となる */
.header-navigation-check {
  cursor: pointer;
  display: block;
  height: 48px;
  margin: 0;
  opacity: 0;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  z-index: 110;
}
/* // チェックボックス_これが :checked かどうかが起点となる */

[7]. チェックボックスが :checked になったことにより連動する各パーツ

さて、この部分が、
「チェックボックスがチェックされているか / いないか」
を判定基準として、
「cssのみで3本線アイコンを動かす」
「cssのみでドロワーメニューを開閉」
という挙動
について記述している部分になります。

上の[6]同様、最も重要な部分になります。

それでは内容について、順番に説明を書いていきます。

まず、ここに着目してください。
.header-navigation-check:checked ~ .header-navigation-icon-container
↑この様に、この部分のcssは全て

.header-navigation-check:checked
↑チェックボックスがチェックされた状態(:checked)を対象としています。

そして、
.header-navigation-check:checked ~ .header-navigation-icon-container
↑のように、すべて「 ~ 」で要素をつなぎ、
チェックボックスがチェックされた状態(:checked)の時に変化する対象
を指定しています。

後続兄弟結合子「 ~ 」

この「 ~ 」は
後続兄弟結合子(後続兄弟セレクター)」と呼ばれるものになります。
はたらきとしては、
2つのセレクターを関係づけて、「 ~ 」の後に続く要素に対して変化を与える事ができる機能を持ちます。

これを使う事で、「チェックボックスがチェックされた状態(:checked)の時に変化する対象」を指定する事ができるというわけです。

ただし、この後続兄弟結合子「 ~ 」を有効にするには条件があって

・「1個目の要素の後」に対象とする要素があること
・1個目の要素と対象とする要素、それぞれが「必ず共通の親要素を直前に持っていること(階層として同じでなければならない)」

↑この2つの条件を揃えると、後続兄弟結合子「 ~ 」が有効になり、
今回のサンプルの様に
チェックボックスがチェックされた状態(:checked)の時に変化する対象
を指定する事ができる様になります。

.header-navigation-check:checked ~ .header-navigation-icon-container
↑3本線アイコンに対する変化の指定です。ドロワーが開いた時に、
3本線アイコンがドロワーよりも上になってほしいので(開いた時は3本線アイコンは閉じるアイコンに変化するため)
z-index でそれを指定しています。

.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon::before
↑これは、3本線アイコンの上の線に対する変化です。
ドロワーが開いた時に、3本線アイコンを閉じるアイコンに変化させるための動きを指定しています。

.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon
↑これは、3本線アイコンの真ん中の線に対する変化です。
ドロワーが開いた時に、3本線アイコンの真ん中の線は見えなくして、閉じるアイコンに変化させるための動きを指定しています。

.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon::after
↑これは、3本線アイコンの下の線に対する変化です。
他の2本の線と同様、ドロワーが開いた時に、3本線アイコンを閉じるアイコンに変化させるための動きを指定しています。

.header-navigation-check:checked ~ .drawer-container
↑この部分の指定で、初期値が right: 300px; と指定していた値が
right: 0; となり、
ドロワー本体が画面に入ってきます

.header-navigation-check:checked ~ .drawer-background
↑そしてこの部分の指定は、ドロワーの下に敷く、全画面のグレー背景になります。
この部分の指定で、ドロワーの下に敷く、全画面のグレー背景が出現します。

/* [7]. チェックボックスが :checked になったことにより連動する各パーツ */
.header-navigation-check:checked ~ .header-navigation-icon-container {
  left: initial;
  position: fixed;
  right: 7px;
  top: 24px;
  z-index: 100;
}
.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon::before {
  opacity: 1;
  top: 50.125%;
  transform: rotate(45deg) translate(-50%, -50%);
  visibility: visible;
}
.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon {
  background: rgba(50, 50, 50, 0);
  visibility: hidden;
}
.header-navigation-check:checked ~ .header-navigation-icon-container .header-navigation-icon::after {
  opacity: 1;
  top: 50.125%;
  transform: rotate(-45deg) translate(-50%, -50%);
  visibility: visible;
}
.header-navigation-check:checked ~ .drawer-container {
  right: 0;
}
.header-navigation-check:checked ~ .drawer-background {
  display: block;
  height: 100vh;
  left: 0;
  position: fixed;
  width: 100vw;
  z-index: 5;
}
/* // チェックボックスが :checked になったことにより連動する各パーツ */

↑以上、[1]〜[7]のcssを記述する事で、次のキャプチャの様に
cssだけでドロワーメニューをつくる」事ができるわけです。

完成したcssのみでつくるドロワーメニューを開いた状態

ドロワーメニューの下の、半透明のグレー背景を押したら閉じる様にしたい

さて、ここまでの記述で、
cssだけでドロワーメニューをつくる」事について書いてきました。

ここにさらにもうひと手間を加えるとすれば、
ドロワーメニューの下の、背景の半透明グレーを押した時に閉じる機能」でしょうか。

これを実現するためにはさすがにcssだけでは不可能なので、
JavaScriptの登場です。

「半透明のグレー背景が押された時」
のイベント( click イベント)を検知
して、
「半透明のグレー背景が押されたらチェックボックスのチェックを外す」
という処理を書いてあげれば実現できます。

具体的には次の通り。

<script>
  const headerNavigation = document.querySelector('.header-navigation-check');
  const drawerBackground = document.querySelector('.drawer-background');
  drawerBackground.addEventListener('click', () => {
    headerNavigation.checked = false;
  });
</script>

↑これを、htmlのbodyの閉じタグ直前あたりに記述してあげれば
ドロワーメニューの下の、背景の半透明グレーを押した時に閉じる」ようになり、
より使いやすいドロワーメニューの完成です。

MDN内の解説ページ

今回は「ドロワーメニュー(ハンバーガーメニュー)をcssだけで作る方法」について書いてきたわけですが、
その根幹をなす仕組みである「後続兄弟結合子」について、
おなじみ MDN に詳しい解説があります。
後続兄弟結合子) – CSS:カスケーディングスタイルシート | MDN

↑このMDN、Webの仕様そのものを決めている団体のひとつ、Mozillaが運営しているリファレンスサイトの解説ですので
やはり一番正確かつ、一番わかりやすいと思います。

この記事のまとめ

今回は、
ドロワーメニュー(ハンバーガーメニュー)をcssだけで作る方法
について記事を書いてみました。

今回ご紹介した、根幹となる仕組みである
「後続兄弟結合子」
を覚えておくと、cssだけで実現できることが意外とたくさんあることに気がつくかと思います。

色々な場面で応用が効く仕組みになりますので、覚えておくと、実際のWeb制作の際に役立つことと思います。

この記事が皆さんのより良いWeb制作体験につながれば、嬉しく思います。

※この記事は内容の品質向上を目的に、随時更新を行う場合があります。

この記事をシェアする: