JavaScriptで階層の深いハンバーガーメニューを作る

こんにちは、ナカムラです。
今回はスマホサイトではお馴染みのハンバーガーメニューの作り方をご紹介します。
タイトルに「階層の深い」と書きました。
ページ数の多いサイトでは、第2階層、第3階層とメニューが深くなっていきます。
階層が深くなるとメニューの数も増えていくため、メニューの中でも開閉するような仕様になっていきます。
そうすると、今度はメニューを閉じる時にも工夫が必要です。

下層メニューを開いた状態で閉じた時、何もしていないと次に開いた時に下層メニューが開いたままになります。
メニューが多い場合はそれが操作の妨げになることもあるため、下層メニューも一緒に閉じるようにすると良いです。
また、ブラウザバックで戻ったときや、同じページ内に遷移先がある場合は、クリックしたら閉じるという処理も必要になります。
今回はこれらに対応したハンバーガーメニューになります。

DEMO

See the Pen JavaScriptで階層の深いハンバーガーメニューを作る by Nakamura (@takayo-nakamura) on CodePen.

ハンバーガーメニューの開閉

const html = document.querySelector('html')
const btn = document.getElementById('btn')
const menu = document.getElementById('menu')

// 下層のメニューを閉じる処理の関数
function subMenuClose () {
  const opens = document.querySelectorAll('.subNavBtn.is-active')

  // IE対策
  const opensList = Array.prototype.slice.call(opens, 0)

  opensList.forEach(e => {
    e.classList.remove('is-active')
  })
}

// ハンバーガーメニューの開閉
btn.addEventListener('click', () => {
  btn.classList.toggle('is-active')
  if (btn.classList.contains('is-active')) {
    // 開く処理
    html.classList.add('is-menuOpen')
  } else {
    // 閉じる処理
    html.classList.remove('is-menuOpen')
    subMenuClose()
  }
})

btnというidに対してクリックイベントを付与し、is-activeというclassを付けたり外したりします。
また、btnにis-activeが付与されている時、HTMLにis-menuOpenを付与します。
classをJavaScriptで制御し、表示はスタイルシートで制御する方法になります。
CSSではis-menuOpen内の時に高さを持たせることで表示するようにしています。

#menu{
  height: 0;
  overflow: hidden;

  .is-menuOpen &{
    height: auto;
  }
}

閉じる処理では開いた状態の下層メニューを探し、開くために付与しているclassを外すことで下層メニューを閉じます。
管理しやすいようにsubMenuClose()という関数に分けています。
下層メニューの開閉については次にご説明します。

下層のメニューの開閉

// 下層のメニューの開閉
const subNavs = document.querySelectorAll('.subNavBtn')

// IE対策
const subNavsList = Array.prototype.slice.call(subNavs, 0)

subNavsList.forEach(e => {
  e.addEventListener('click', () => {
    e.classList.toggle('is-active')
  })
})

先ほどと同じことをしていますが、下層メニューは複数入ることが多いため、対象をidではなくclassにしています。 subNavBtnというclassに対してクリックイベントを付与します。
ほぼ同じことをしているので細かい説明は省略いたします。
詳しくはDEMOのソースをご確認ください。

メニューをクリックしたら閉じる

// メニューをクリックしたら閉じる
const links = document.querySelectorAll('.globalNav a')

// IE対策
const linksList = Array.prototype.slice.call(links, 0)
  linksList.forEach(e => {
    e.addEventListener('click', function () {
      const btn = document.getElementById('btn')
      btn.click() // ハンバーガーメニューのボタンをクリックする
    })
})

こちらはメニューの中のリンクを押した時に付与するイベントです。
閉じる動作をもう一度書いたり、htmlタグへのclassの付与/削除もまとめて関数にしておいても良いですが、今回は「.click()」を使ってみました。
こちらはjQueryのtriggerメソッドと同じようにクリックした状態にすることができます。
なので、最初に書いたハンバーガーメニューのボタンを押した後の処理が走るため、下層メニューも含めて全て閉じることができます。

最後に

ナビゲーションはページの閲覧の邪魔にならないように、かつ必要な時には使いやすいように様々な工夫が必要になります。 今回はだいぶシンプルに書きましたがアニメーションを加えることでさらに操作方法がわかりやすくなります。 いろいろと工夫していきたいと思います。