ハンバーガーメニュー
三本線アイコンが × に変形しながらメニューが横からスライドイン。iframe内対応のposition:absolute実装。
プレビュー
操作クリック/タップで動きます。
仕様書
- 向く場面
- モバイル幅でヘッダーに収まらない4〜6項目のナビ
- ロゴとボタンだけで狭い横幅を圧迫したくない
- たまに開くグローバルナビ
- 避ける場面
- PC幅で常時横並びにできる余白がある画面
- 何度も開閉するカテゴリ切替(タブバー向き)
- 開いている間に背景をスクロールさせたい画面
- 構成要素
- ハンバーガーボタン
- ドロワー
- メニューリンク
- オーバーレイ
- 持っている状態
- 通常
- ホバー
- 押下中
- キーボード
Tab で☰ボタンへ・Enter/Space で開閉。開くと先頭リンクへフォーカスが移り Tab で各リンクへ・Esc で閉じてボタンへ戻る。オーバーレイのクリックでも閉じる
- 支援技術
ボタンに aria-expanded と aria-label、ドロワーに aria-hidden と inert を開閉と同期(閉じている間はリンクに Tab が入らない)。選んだリンクに aria-current=page
- 動き
ドロワー translateX 0.3s・オーバーレイ opacity 0.3s・三本線→× 0.28s ease。横から現れる位置の連続性。reduced-motion では即時
デザインの要点
- 閉じている間はドロワーに inert を付ける。translateX で画面外に出しただけの要素は Tab で届き、見えないリンクにフォーカスが吸われる
- 開いたら先頭リンクへ、閉じたら☰へフォーカスを移す。開閉のたびにフォーカスが body へ落ちると、次の Tab が先頭からやり直しになる
- 三本線→× は同じボタンの中で変形させる。閉じる操作が開いた操作と同じ場所にあることを形で示す
- オーバーレイは opacity と pointer-events を同時に切り替える。透明なまま pointer-events が残ると、閉じた後も背景が押せない
使いどころ
ナビ項目が4〜6個程度あり、モバイル幅ではヘッダーに収まりきらない画面に向く。ロゴとハンバーガーボタン1つだけを常時表示し、必要なときだけドロワーを開くことで、狭い横幅を圧迫しない。逆に、PC幅で常時ナビを横並び表示できる余白があるなら、開閉の一手間を挟むこのパターンは不要で、通常のヘッダーナビの方がクリック数が少なく済む。また、画面遷移のたびに何度もメニューへ戻る用途(ECのカテゴリ切替など)では開閉の摩擦がストレスになりやすいので、タブバーやボトムナビのように常時見えるナビゲーションの方が向くことが多い。三本線が×へ変形しつつ右からドロワーがスライドインし、背後に暗いオーバーレイが重なるという、モバイルUIで最も定番の組み合わせになっている。
仕組み
三本線から×への変形は、1本目をtranslateY(7px) rotate(45deg)、2本目をopacity:0とscaleX(0)で消し、3本目をtranslateY(-7px) rotate(-45deg)にすることで上下のバーが中央で交差して×に見える仕組み。transform-originをcenterにしているため回転軸がぶれない。ドロワー本体はtransform: translateX(100%)からtranslateX(0)への移動だけで出し入れし、z-index 20でオーバーレイ(z-index 10)より前面に重ねる。open()/close()の2関数がis-openクラスとaria-expanded/aria-hidden属性をまとめて同期させる設計。見落とせない点として、.hb-root自体がposition:relative; overflow:hidden; width:360px; height:240pxのカード枠になっており、.hb-drawerはposition:absoluteでこのルート基準に配置されている。これはiframeプレビュー内で完結させるための実装で、実ページに組み込むときは.hb-drawerと.hb-overlayをposition:fixedに変え、hb-rootのoverflow:hiddenを外さないと、ドロワーが親要素の高さで切り取られてしまう。
実装の注意
最大の注意点は、このデモのposition:absolute実装をそのまま本番のヘッダーへ移植すると、ドロワーが親要素の高さ(240px)で切れてしまうこと。実際の全画面ドロワーにするには.hb-drawerと.hb-overlayをposition:fixedに変え、hb-root側のoverflow:hiddenの制約を外す作業が要る。ドロワー内のリンクはhref="#"のままだとクリックでページ先頭へ遷移してしまうため、デモではe.preventDefault()で止めてからclose()を呼んでいるが、実装時はこのpreventDefaultを外し、実際のURLへ書き換えるのを忘れないこと。もう一つ、開いている間に背景(.hb-main)がスクロールできてしまう点も手当てされていない。長いコンテンツの裏でドロワーを開くと、指でスクロールしたつもりが背景ごと動く体験になるため、開いている間はbodyにoverflow:hiddenを当てるなどの背景スクロールロックを別途足す必要がある。
アクセシビリティ
aria-expandedはhb-btnで、aria-hiddenとinertはhb-drawerで、それぞれopen()/close()に合わせて同期している。閉じている間ドロワーはtranslateX(100%)で画面外にあるだけなので、何もしないとTabキーで見えないリンクにフォーカスが入ってしまう(aria-hiddenだけでは読み上げツリーから外れるだけでフォーカス制御にはならない)。そこでis-openがfalseの間は.hb-drawerにinert属性を付け、中のリンクをタブ順とクリック対象から丸ごと外している。これがtabindex="-1"を1本ずつ付け外しするより確実で、リンクを増やしても漏れない。フォーカス管理は、開いたときにドロワー内の最初のリンクへ移動し、Escキーまたはオーバーレイ・リンクのクリックで閉じたときにトリガーのhb-btnへ戻す形で実装してあり、開閉のたびにフォーカスがbodyへ落ちてTabが先頭からやり直しになる事故を防いでいる。選んだリンクにはaria-current="page"を付けて現在地を示す。開いている間のフォーカストラップ(Tabがドロワーの外へ出ないようにする)は本番実装では追加すべき。
調整して使う
触る値はドロワーの出現方向(右から/左から)、幅(現在60%、固定280pxなどに変える手もある)、オーバーレイの濃さと有無、開閉のtransition時間(現在0.3s)。三本線の変形を×ではなく矢印やチェックに変える表現もあるが、その場合は各barのtransform値をアイコンの形に合わせて作り直す必要がある。ドロワーをoverflow:hiddenの親から解放してposition:fixedにすれば、幅を100%にした全画面メニュー(オーバーレイ不要)にも展開できる。ロゴとボタンだけのヘッダーではなく、検索アイコンなど他のアクションを右側に並べる構成にする場合は、hb-header内のjustify-content: space-betweenを崩さないよう配置し直すことになる。
コード(コピーして使えます)
<div class="hb-root">
<header class="hb-header">
<span class="hb-logo">MyApp</span>
<button class="hb-btn" aria-label="メニューを開く" aria-expanded="false">
<span class="hb-bar"></span>
<span class="hb-bar"></span>
<span class="hb-bar"></span>
</button>
</header>
<nav class="hb-drawer" aria-label="メインメニュー" aria-hidden="true" inert>
<ul>
<li><a href="#">ホーム</a></li>
<li><a href="#">サービス</a></li>
<li><a href="#">実績</a></li>
<li><a href="#">お問い合わせ</a></li>
</ul>
</nav>
<div class="hb-overlay"></div>
<main class="hb-main">
<p class="hb-hint">右上の ☰ をクリック</p>
</main>
</div>