JavaScriptとjQueryでスムーススクロールに対応したハンバーガーメニューを作ってみた

調べるといっぱいやり方が出てくるが、スムーススクロールと組み合わせた時に動かなかったり、ハンバーガー内のメニューをクリックした時に閉じてくれなかったりなど、色々と問題が起こることが多かったので、自分なりにい組み合わせて上手くいったものをコードとして残しておくこととする。

今回は、ふわっとメニューが現れるパターン、上からメニューがスライドしてくるパターンで作ってみた。
ハンバーガーの線は3本線で、クリックすると、真ん中の線が右へ消えていくように実装している。

本当はJavaScriptだけでやってみたかったのだが、今の自分では考えてもうまく実装できなかったため、一部jQueryも用いて対応している。もう少しスキルが上がったら、脱jQueryのハンバーガーメニューに挑戦したいと思う。

下記にデモを用意している。demo01はフワッとメニューの中身が出てくるパターンで、demo02はメニューが上から降りてくるパターンだ。

では、早速やり方に入っていきたいと思う。

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="reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>
    <!-- ハンバーガーメニュー -->
    <!-- 線 -->
    <div id="bl_hamburgerLine">
      <span></span>
      <span></span>
      <span></span>
    </div>
    <!-- メニューの中身 -->
    <nav id="bl_hamburgerMenu">
      <ul class="bl_hamburgerMenu_list" id="bl_hamburgerLink">
        <li><a href="#menu1">menu1</a></li>
        <li><a href="#menu2">menu2</a></li>
        <li><a href="#menu3">menu3</a></li>
        <li><a href="#menu4">menu4</a></li>
        <li><a href="#menu5">menu5</a></li>
      </ul>
    </nav>
    <!-- //ハンバーガーメニュー -->
  </header>
  
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <script src="style.js"></script>
</body>
</html>

まず、cssやjsの読み込み先は作成する人によってパスの記述が異なるので、それぞれの環境にあうパスの記述を指定することが必要だ。

headerの中にハンバーガーメニューを記述している。idを各所で指定しているのは、後ほどjsで指定して使用するためだ。ちなみに、idやclass名は任意の名前で大丈夫だ。

ハンバーガーメニューの3本線について

ハンバーガーメニューの3本線は、コメントアウトで「線」と記述している部分に当たる。spanを3つ分記述することで、線を3本配置するイメージだ。
2本でよければspanの記述を削除すればいいが、cssで調整が必要になる。この記事ではやり方は紹介しないが作成することも可能だ。また、工夫次第では、2本線だけでなくテキスト入りのハンバーガーメニューなども作ることができるだろう。

ハンバーガーメニューの中身について

3本線をクリックして、展開された時のイメージだ。

大抵の場合は、メニューが展開されているときは周囲が暗くなったり透過されていることが多いと思う。今回は背景は透過させずにメニューが展開されるように実装している。

ハンバーガーメニューは、ページでいうと各コンテンツのタイトル部分であったり、他の主要ページのタイトル部分へのリンクであることが多いため、navタグで囲って作成している。

今回はLPページをイメージして、ページ内リンクで各メニューにアンカーリンクをつけて対応した。

cssの記述について

@charset "utf-8";

/* ==========================
  ハンバーガーメニュー
========================== */
header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px;
}
/* 線 */
#bl_hamburgerLine {
  position: absolute;
  right: 0;
  top: 0;
  width: 60px;
  height: 60px;
  overflow: hidden;
  margin: 0 10px;
  cursor: pointer;
}
#bl_hamburgerLine span {
  display: block;
  position: absolute;
  right: 0;
  background-color: #444;
  height: 2px;
  width: 100%;
  transition: .3s;
}
#bl_hamburgerLine span:first-of-type {
  top: 10px;
}
#bl_hamburgerLine span:nth-of-type(2n) {
  top: 30px;
}
#bl_hamburgerLine span:last-of-type {
  top: 50px;
}
/* クリックした時 */
#bl_hamburgerLine.active {
  z-index: 1001;
}
#bl_hamburgerLine.active span {
  background-color: #fff;
}
#bl_hamburgerLine.active span:first-of-type {
  transform: rotate(135deg);
  top: 30px;
}
#bl_hamburgerLine.active span:nth-of-type(2n) {
  transform: translateX(100%);
}
#bl_hamburgerLine.active span:last-of-type {
  transform: rotate(-135deg);
  top: 30px;
}

/* ハンバーガー展開時の中身 */
#bl_hamburgerMenu {
  opacity: 0;
  visibility: hidden;
  transition: .3s;
  position: absolute;
  width: 100%;
  height: 100vh;
  background-color: #444;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  font-size: 24px;
  overflow-y: scroll;
}
.bl_hamburgerMenu_list li+li {
  margin-top: 20px;
}
/* クリックした時 */
#bl_hamburgerMenu.active {
  z-index: 1000;
  opacity: 1;
  visibility: visible;
}

ハンバーガーメニューを固定するための指定

headerにposition: fixed;を指定している。これによりスクロールした時でも位置が固定できるようになる。

ただ、これだけの指定だと、どこの位置で固定させたいかの指定ができていないので、top、bottom、left、rightを使って表示させたい位置に指定する必要がある。

今回はheaderに直接指定しているが、headerの中身の要素が多くなった場合は、header自体に直接指定しないほうがいい場合もあるので、その時はハンバーガーメニューに直接指定することをおすすめする。

ハンバーガーメニューの線を作る

まずはどのくらいの大きさで作りたいかを決める。今回は60pxの正方形で作成している。大枠の正方形ができたら、positionでどの位置に配置したいかを決める。今回はスマホを右手で触る人が多いと思うので、右上に配置した。

あとはspanで背景色を使って線を作っていく。spanはインライン要素なのでブロック要素にして今回は対応している。背景色を変えれば3本線の色が変わるし、線の太さを調整したければ、heightの値を調整するとアレンジができるようになっている。

3本線をクリックした時の×ボタンを作る

クリックした時のクラスはactiveを使うことが多い。

例えば、カレンダーなどの切り替えタブや、アコーディオンが展開された時などでも、選択されたタブや展開されているアコーディオンに対してactiveを使うことが多い。なので、今回はactiveを使用している。

3本線をクリックした時に、activeというクラスを付与するのは、JavaScript側で行うため、cssではactiveが付いた状態の時にどういう表示になっていれば良いかを考えながら組んでいくとイメージが浮かびやすいかと思う。

今回はspanで作った線を斜め45度に傾ければ、×印を作れそうだ。3本線で言うと、一番上の線と、一番下の線を傾ければ対応できそうだ。

真ん中の線はどうするかというと消してしまえばいい。今回はただ消すだけだとつまらないので、右側に移動しながら消えるように対応している。
これを実現するには、箱からはみ出た時に中の要素が表示されないようにoverflowをhiddenに設定する必要がある。

また、2本目の線に対して、transformでその線自体を100%分右側に移動する必要性が出てくるので、trasnlateXで横方向に100%分移動する指定をした。

あとはアニメーションの秒数を指定するのだが、移動する前の要素に対してしてすることでアニメーションが実装できるので、0.3秒で今回は指定している。また、animationプロパティは秒数以外にもいろんな指定ができるので、もっと凝ったものを作りたければ色々試してみると面白いかもしれない。

ハンバーガー展開時の中身の作成

まずは、opacityやvisibilityの記述はコメントアウトした状態で、作ってみるほうが個人的には良さそうだ。

なぜかというと、展開された状態のイメージが検証ページで反映された状態になるので、修正箇所があれば気付きやすいし、完成イメージもつきやすいからだ。ここである程度ページができてからopacityやvisibilityのコメントアウトを外して非表示にすることをおすすめする。

ここでのactiveのクラスの考え方は、ハンバーガーの線の部分と同様で、クリックする前と後で、opacityやvisibilityを使って表示を分けているだけだ。

ここで何故displayをnoneにしてはダメなのかという疑問も出てくるかもしれない。これはアニメーションが動かなくなる関係で使っていない。何故かについては、display、opacity、visibilityでググるとたくさん出てくるので調べてみて欲しい。

overflow-yにスクロールをきかせているのは、メニューが多くなり画面よりも外側にはみ出る場合に対応できるようにしている。

JavaScriptの記述について

'use strict';

// ハンバーガーメニュー
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('bl_hamburgerLine').addEventListener('click', function () {
    this.classList.toggle('active');
    document.getElementById('bl_hamburgerMenu').classList.toggle('active');
  });
  // メニュークリックで閉じる
  $('#bl_hamburgerMenu > .bl_hamburgerMenu_list a[href]').on('click', function (event) {
    $('#bl_hamburgerLine').trigger('click');
  });
});

// スムーススクロール
window.addEventListener('DOMContentLoaded', function () {
  const anchorLinks = document.querySelectorAll('a[href^="#"]');
  const anchorLinksArr = Array.prototype.slice.call(anchorLinks);

  anchorLinksArr.forEach(function (link) {
    link.addEventListener('click', function (e) {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top; //ここに- 50 などと数値を入れるとヘッダー固定のスクロールが実現できる
      window.scrollTo({
        top: targetOffsetTop,
        behavior: "smooth"
      });
    });
  });
});

jsでハンバーガーメニュを実装する

ページが読み込まれてから、id「bl_hamburgerLine」(任意の名前でOK)を取得し、それをクリックした時に、activeのクラスが付いたり消えたりするように設定している。

また、メニューをクリックした時に、ハンバーガーメニューを閉じるようにもしたいので、jQueryを用いてメニューの各リンクをクリックした時でも、右上の3本線をクリックした時と認識させるように指定をした。

jsでスムーススクロールを実装する

これはテンプレみたいなものなので、調べると色々やり方が出てくると思う。

querySelectorAllでページ内の全てのa[href^=“#”]に対して指定する。
配列に変換するために、Array.prototype.slice.call(anchorLinks)を指定。

配列に変換して各a要素に対して、forEachを用いて処理を指定していく。
クリックした時に他のページに遷移しないようにするため、e.preventDefault()を指定。

続いてlink.hashによりid名「#●●」を取得し、これだけだとidを取得したに過ぎないので、document.querySelector(targetId)で別途指定することでページ内の該当要素を取得できる。

あとはページの一番上の距離とターゲット要素までの距離、さらに現在どこのページの位置にいるのかということが分かればOKだ。そこから計算してスクロールする量をコントロールしていけばいいだろう。

簡単にいうと、現在見ているページの位置からアンカーリンク先の距離を計算できれば良いことになる。

まず、window.pageYOffsetでページの一番上から現在のページの位置を距離を取得する。これで現在位置がわかるようになった。

あとは先ほど指定した各idに対して、getBoundingClientRect().topを指定することで、現在見ているページ画面の上側(※ページの一番上側ではないため注意)からアンカーリンク先のidの距離を取得できる。

あとはこれらを足し算することで、移動距離が求められる。今回はハンバーガーメニューのため、ヘッダーの高さ分は考慮していない。例えば、ハンバーガーメニュー以外のナビゲーションの場合は、ヘッダーの高さが固定されることがあるので、その場合は、足し算して求められた移動距離からヘッダー分の高さを引き算すれば良い。また、ここではあげていないが、PC幅やスマホ幅でヘッダーの高さが変わる場合は条件別に定義する必要があるので注意すべきポイントだ。

ここでの移動距離について、どうして足し算で求められるのか疑問に思う方もいるかもしれないので、簡単な例としてあげておきたい。

例えば、現在画面領域の一番上端は100の位置でページを閲覧しているとする。この条件の時に、画面領域の一番上端を200の位置まで移動するにはどのくらいの距離が必要だろうか。

こういう問題があったときに、100と距離が求められると思うが実際にはどのように計算しているだろうか。
単純に計算すれば⇨200-100
先ほどのコードに例えて計算すると
⇨100+(200-100)
画面上端の距離+(アンカーリンク先の距離-画面上端の距離)

こうやって見てみると、きちんと式が成り立っていることがわかるだろう。

最後にwindow.scrollToでconstで指定した現在位置からアンカーリンク先までの距離targetOffsetTopを指定し、スクロールを滑らかにするために、behavior: “smooth”を指定すれば完成になる。

色々自己流に理解して落とし込んだつもりだが、要所要所でおかしなところがあるかもしれないので、何かあったらご指摘いただければと思う。

ちなみに、PC幅は通常のナビゲーションメニュー、スマホ幅はハンバーガーメニューで分けて対応したい場合、下記のページも参考にして見てほしいと思う

PC幅はヘッダー固定のナビゲーションメニュー、スマホ幅はハンバーガーメニューのサンプルを作ってみた(備忘録)

1 返信

コメントはクローズされています。