はじめに
今やあらゆるサイトで使用されているハンバーガーメニュー。
私たちウェブサイト系のエンジニアは数え切れないくらい実装してきている定番パーツだ。
しかしながらその実装は容易ではない。
ただ単にそれっぽく動くものを作るのであれば簡単だが、アクセシビリティやキーボード操作、メニュー展開時の他の箇所の挙動など、不具合がないように徹底的に作り込もうとするとなかなか難しい。
今回はそんな厄介な存在であるハンバーガーメニューについて、
私なりの現在の実装方法を紹介したい。
ハンバーガーメニューを実装するうえで気を付けるべきこと
ハンバーガーメニューを実装する前に、
ハンバーガーメニューのあるべき姿、実装するうえで気にかけるべきことを列挙してみよう。
私が思い浮かぶものだと以下のようなものになる。
- メニューボタン、アコーディオンボタンなどはボタンであること。
- メニューの開閉状態がスクリーンリーダーに伝わる。
- メニュー展開時に背景がスクロールしないように固定する。
- 各要素にフォーカスがあたり、キーボード操作で操作が可能。
- メニューが閉じている状態ではメニュー項目にフォーカスが当たらないようにする。
- アコーディオンメニューがある場合、メニューが閉じている状態でアコーディオンメニューの中身にフォーカスが当たらないようにする。
- エスケープキーでメニューを閉じられるようにする。
- メニューを閉じた際にメニューボタンにフォーカスが戻るようにする。
- キーボード操作時、メニューの最後の項目までいったらメニューボタンもしくはメニューの先頭にフォーカスが戻るようにする。
ざっとこんなところだろうか。
今回はこれらの点に留意した実装を紹介する。
実装方法
まずは完成形を見ていただこう。
See the Pen humberger by raikikannobaigie (@raikikannobaigie) on CodePen.
順にポイントを解説する。
ポイント1 メニューボタンはbutton要素で。wai-area属性を付ける。
まず基本的なことになるが、フォーカスが当たりキーボードで操作可能であり、
かつスクリーンリーダーにメニューを開閉するボタンであることが伝わるように、
メニューボタンはbutton要素を使用して実装する。
加えて、メニューの開閉状態を伝える属性「aria-expanded」と、
このボタンが制御しているメニューはどれなのかということを示す「aria-controls」属性を付与してやる。
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-area属性を付けて実装する。
メニューボタンと同様にbutton要素を使用し、area-expandedなどを付与する。
ポイント6 エスケープキーでメニューを閉じられるようにする。
キーボード操作ではエスケープキーで項目を閉じられるような仕様が多くみられ、
ユーザーにとってもこれは直感的な操作として広く浸透しているので、
ハンバーガーメニューにおいてもエスケープキーでメニューを閉じられる仕様にしておくことは、使い勝手向上のためにも重要だ。
今回はevent.keyで押下されたキーを取得し、それがエスケープキーだったらメニューが閉じられるように実装している。
またその際に、メニューボタンにfocus()を指定することで、メニューを閉じた際にメニューボタンにフォーカスが当たり、すぐさま再びメニューを開くことができるようになっている。
ポイント7 キーボード操作時、メニューの最後の項目までフォーカスが移ったらメニューボタンかメニューの最初の項目にフォーカスを戻す。
これもまたよく見る例だが、メニューの最後の項目までキーボード操作でフォーカスを移動させると、その次は背景である本文エリアにフォーカスが移ってしまう実装をよく見かける。
これではユーザーは自分が今どこを操作しているのか分からないし、
メニュー項目の操作も一苦労だ。
なのでメニューの最後の項目までフォーカスが移動したら、メニューボタンかメニューの最初の項目にフォーカスを移動させてやる必要がある。
様々な実装方法があるが、今回はフォーカストラップという手法を利用した。
<div id="js-focus-trap" tabindex="0"></div>
という要素があるがこれがフォーカストラップである。
この要素にフォーカスが当たった際に、JSで制御し、メニューボタンか先頭項目にフォーカスを当てるという仕組みだ。
まとめ
今回ご紹介した実装方法はあくまで私個人の考えに基づくものであるので、
このやり方の方が良いのではないか、などある方はぜひともご意見いただきたい。