ハンバーガーメニューの実装方法決定版

かんののプロフィール画像
エンジニアかんの

はじめに

今やあらゆるサイトで使用されているハンバーガーメニュー。

私たちウェブサイト系のエンジニアは数え切れないくらい実装してきている定番パーツです。

しかしながらその実装は容易ではありません。

ただ単にそれっぽく動くものを作るのであれば簡単ですが、アクセシビリティやメニュー展開時の他の箇所の挙動など、不具合がないように徹底的に作り込もうとするとなかなか難しいものです。

今回はそんな厄介な存在であるハンバーガーメニューについて、私なりの現在の実装方法を紹介したいと思います。。


ハンバーガーメニューの実装で気を付けるべきこと

ハンバーガーメニューを実装する前に、ハンバーガーメニューのあるべき姿、実装時に気を付けるべきことを洗い出してみましょう。

私が思い浮かぶものだと以下のようなものがあります。

  • メニューボタン、アコーディオンボタンなどはbutton要素である。
  • メニューの開閉状態がスクリーンリーダーに伝わる。
  • メニュー展開時に背景がスクロールしないように固定する。
  • 各要素にフォーカスがあたり、キーボードで操作が可能。
  • メニューが閉じている状態ではメニュー項目にフォーカスが当たらないようにする。
  • アコーディオンメニューがある場合、メニューが閉じている状態でアコーディオンメニューの中身にフォーカスが当たらないようにする。
  • エスケープキーでメニューを閉じられるようにする。
  • メニューを閉じた際にメニューボタンにフォーカスが戻るようにする。
  • キーボード操作時、メニューの最後の項目までいったらメニューボタンもしくはメニューの先頭にフォーカスが戻るようにする。

今回はこれらの点に留意した実装を紹介しようと思います。

実装方法

まずは完成形をご覧ください。

See the Pen humberger by raikikannobaigie (@raikikannobaigie) on CodePen.

順番にポイントを解説していきます。

ポイント1. メニューボタンはbutton要素で実装し、WAI-ARIA属性を付ける。

まず基本的なことですが、フォーカスが当たりキーボードで操作可能であり、かつスクリーンリーダーにメニューを開閉するボタンであることが伝わるように、メニューボタンはbutton要素を使用して実装します。

加えて、メニューの開閉状態を伝える属性「aria-expanded」と、このボタンが制御しているメニューはどれなのかということを示す「aria-controls」属性を付与します。

メニューの展開は、クラスの有無でCSSで制御するようにします。

ボタンが押下されたらJSでメニューボタンとメニューにクラスを付与し、メニューが展開するようにします。
同時に、JSでメニューボタンの「aria-expanded」属性の属性値をtrueに書き換え、メニューが展開したことをスクリーンリーダーに伝えます。

ポイント2. メニュー展開時は背景を固定する。

メニューが展開した際、背景(メニューの裏に隠れている本文、メインコンテンツエリア)がスクロールできてしまう実装をよく見かけます。
これではメニューの操作性も悪くなりますし、メニューを閉じたらコンテンツ内の全然違う箇所に移動していたりと、全体的に使い勝手が悪くなります。
それらを解決するために、今回の例では「backgroundFix」と定義してある関数を用いて、メニュー展開時に背景を固定しています。

htmlタグやbodyタグにoverflow: hidden;を指定してスクロールを防ぐやり方もありますが、そのやり方だとiOSに対応できません。

今回の実装の仕組みとしては、まずコンテンツのスクロール位置を取得し、それをもとにposition: fixed; height: 100vh;で背景を固定します。メニューを閉じた際はこれを解除し、先に取得したスクロール位置を使用してもとのスクロール位置に戻してやるといった仕組みです。

ポイント3. メニューが閉じた状態ではメニューの中身にフォーカスが当たらないようにする。

これについてはCSSでvisiblity: hidden;を指定してやればフォーカスが当たりません。
メニュー展開時にJSで値をvisibleに変えます。

ポイント4. アコーディオンメニューが閉じた状態で、アコーディオンメニューの中身にフォーカスが当たらないようにする。

上記ポイント3と同じです。

ポイント5. アコーディオンボタンもbutton要素でWAI-ARIA属性を付けて実装する。

メニューボタンと同様にbutton要素を使用し、area-expandedなどのWAI-ARIA属性を付与します。

ポイント6. エスケープキーでメニューを閉じられるようにする。

キーボード操作ではエスケープキーで項目を閉じられるような仕様が多くみられ、ユーザーにとってもこれは直感的な操作として広く浸透していると考えられます。
それを踏まえると、ハンバーガーメニューにおいてもエスケープキーでメニューを閉じられる仕様にしておくことは、使い勝手向上のためにも重要です。

今回はevent.keyで押下されたキーを取得し、それがエスケープキーだったらメニューが閉じられるように実装しています。
またその際に、メニューボタンにfocus()を指定することで、メニューを閉じた際にメニューボタンにフォーカスが当たり、すぐさまメニューボタンの操作を行えるようにしています。

ポイント7. キーボード操作時、メニューの最後の項目までフォーカスが移ったらメニューボタンかメニューの最初の項目にフォーカスを戻す。

メニューの最後の項目までキーボード操作でフォーカスを移動させると、その次は背景である本文エリアにフォーカスが移ってしまう実装をよく見かけます。

これではユーザーは自分が今どこを操作しているのか分かりませんし、メニュー項目の操作も一苦労です。

なのでメニューの最後の項目までフォーカスが移動したら、メニューボタンかメニューの最初の項目にフォーカスを移動させてやる必要があります。

様々な実装方法がありますが、今回はフォーカストラップという手法を利用しました。

以下の要素がフォーカストラップと呼ばれるものになります。

<div id="js-focus-trap" tabindex="0"></div>


この要素にフォーカスが当たった際に、メニューボタンか先頭項目にフォーカスを当てるようにJSで制御しています。

まとめ

今回ご紹介した実装方法はあくまで私個人の考えに基づくものですので、このやり方の方が良いのではないか、などある方はぜひともご意見いただきたいです。

こんな記事も読まれています

俺流フロントエンドのディレクトリ構成と設計の考え方
アマノのプロフィール画像
アマノ
WordPressの外観カスタマイズでノーコードのような機能を実装する方法
さかっちょのプロフィール画像
さかっちょ
ウェブアプリ開発にTailwind CSSを導入してみた
金 伯冠のプロフィール画像
金 伯冠
上に戻る