スマートフォンやタブレットでの閲覧が当たり前になった現在、限られた画面幅でナビゲーションをコンパクトにまとめるハンバーガーメニューはほぼすべてのサイトで採用されています。この記事では、HTML・CSS・JavaScript だけでライブラリを使わずに実装する方法を、アニメーション・アクセシビリティ・レスポンシブ対応まで含めて解説します。
・ハンバーガーメニューの HTML・CSS・JavaScript 基本構成
・三本線アイコンの CSS 描画と ×(閉じる)アニメーション
・スライドインのトランジション
・オーバーレイ(背景暗転)の実装
・メニュー外クリック・Esc キーで閉じる処理
・aria-expanded / aria-controls のアクセシビリティ対応
・レスポンシブで PC 幅では通常メニューに切り替える方法
HTML の構成
ハンバーガーメニューの HTML は「ボタン」「ナビゲーション」「オーバーレイ」の 3 要素で構成します。
<header class="header">
<a href="/" class="header-logo">MySite</a>
<!-- ハンバーガーボタン -->
<button class="hamburger" id="hamburger"
aria-controls="nav-menu"
aria-expanded="false"
aria-label="メニューを開く">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</button>
<!-- ナビゲーション -->
<nav class="nav-menu" id="nav-menu">
<ul class="nav-list">
<li><a href="#home">ホーム</a></li>
<li><a href="#about">会社概要</a></li>
<li><a href="#service">サービス</a></li>
<li><a href="#contact">お問い合わせ</a></li>
</ul>
</nav>
<!-- オーバーレイ(背景暗転) -->
<div class="overlay" id="overlay"></div>
</header>
aria-controls でボタンと操作対象のナビゲーションを紐付け、aria-expanded で開閉状態をスクリーンリーダーに伝えます。aria-label でボタンの役割を明示します。
三本線アイコンを CSS で描画する
ハンバーガーアイコンは span 3 本を CSS で横線として描画します。テキストの「☰」やアイコンフォントではなく CSS で描くと、×印への変形アニメーションが実現できます。
/* ハンバーガーボタン */
.hamburger {
display: flex;
flex-direction: column;
justify-content: center;
gap: 6px;
width: 40px;
height: 40px;
padding: 8px;
background: none;
border: none;
cursor: pointer;
z-index: 110;
}
/* 三本線の各ライン */
.hamburger-line {
display: block;
width: 24px;
height: 2px;
background-color: #333;
border-radius: 1px;
transition: transform 0.3s ease, opacity 0.3s ease;
transform-origin: center;
}
×印への変形アニメーション
メニューが開いた状態(.is-open)で 1 本目を 45° 回転、2 本目を非表示、3 本目を -45° 回転すると ×(閉じる)アイコンになります。
/* メニューが開いた時: 三本線 → × に変形 */
.hamburger.is-open .hamburger-line:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.is-open .hamburger-line:nth-child(2) {
opacity: 0;
}
.hamburger.is-open .hamburger-line:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
translateY の値はラインの高さ(2px)+ gap(6px)= 8px です。ラインの太さや間隔を変える場合は、この値も合わせて調整してください。スライドインメニューの CSS
ナビゲーションメニューは画面右側に固定し、通常は画面外に隠しておきます。.is-open クラスが付与されたときに transform: translateX(0) でスライドインさせます。
/* ナビゲーションメニュー */
.nav-menu {
position: fixed;
top: 0;
right: 0;
width: 280px;
height: 100vh;
background-color: #fff;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
transform: translateX(100%); /* 画面外に隠す */
transition: transform 0.3s ease;
z-index: 100;
padding: 80px 24px 24px;
overflow-y: auto;
}
/* メニューが開いた状態 */
.nav-menu.is-open {
transform: translateX(0); /* 画面内にスライドイン */
}
/* メニューリスト */
.nav-list {
list-style: none;
margin: 0;
padding: 0;
}
.nav-list li {
border-bottom: 1px solid #eee;
}
.nav-list a {
display: block;
padding: 16px 0;
color: #333;
text-decoration: none;
font-size: 16px;
}
.nav-list a:hover {
color: #0284c7;
}
オーバーレイ(背景暗転)の CSS
/* オーバーレイ */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
z-index: 90;
}
/* オーバーレイが表示された状態 */
.overlay.is-open {
opacity: 1;
visibility: visible;
}
display: none ではなく visibility: hidden + opacity: 0 を使うことで、transition によるフェードイン・フェードアウトが実現できます。JavaScript でメニューの開閉を制御する
document.addEventListener("DOMContentLoaded", () => {
const hamburger = document.getElementById("hamburger");
const navMenu = document.getElementById("nav-menu");
const overlay = document.getElementById("overlay");
// メニューの開閉を切り替える
function toggleMenu() {
const isOpen = hamburger.classList.toggle("is-open");
navMenu.classList.toggle("is-open");
overlay.classList.toggle("is-open");
// アクセシビリティ: 開閉状態を通知
hamburger.setAttribute("aria-expanded", isOpen);
hamburger.setAttribute("aria-label", isOpen ? "メニューを閉じる" : "メニューを開く");
// メニュー開閉時にスクロールを制御
document.body.style.overflow = isOpen ? "hidden" : "";
}
// メニューを閉じる
function closeMenu() {
hamburger.classList.remove("is-open");
navMenu.classList.remove("is-open");
overlay.classList.remove("is-open");
hamburger.setAttribute("aria-expanded", "false");
hamburger.setAttribute("aria-label", "メニューを開く");
document.body.style.overflow = "";
}
// ハンバーガーボタンをクリック
hamburger.addEventListener("click", toggleMenu);
// オーバーレイをクリックで閉じる
overlay.addEventListener("click", closeMenu);
// メニュー内リンクをクリックで閉じる
navMenu.querySelectorAll("a").forEach(link => {
link.addEventListener("click", closeMenu);
});
// Esc キーで閉じる
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeMenu();
});
});
この実装には以下の機能が含まれています。
- ハンバーガーボタンのクリックでメニューを開閉
- オーバーレイのクリックでメニューを閉じる
- メニュー内のリンクをクリックするとメニューが閉じる
- Esc キーでメニューを閉じる
- aria-expanded の自動切り替え
- body の overflow: hiddenでメニュー展開中の背景スクロールを防止
メニュー外クリックで閉じる処理の仕組み
上記の実装ではオーバーレイのクリックで閉じていますが、オーバーレイを使わない場合は document への click イベントで制御します。
document.addEventListener("click", (e) => {
// メニューまたはハンバーガーボタンの内部であれば何もしない
if (navMenu.contains(e.target) || hamburger.contains(e.target)) {
return;
}
// それ以外の場所がクリックされたらメニューを閉じる
closeMenu();
});
contains(e.target) を使って、クリックされた要素がメニューの内部かどうかを判定します。この判定なしだと、メニュー内のリンクをクリックした瞬間にメニューが閉じてしまいます。レスポンシブ対応(PC 幅では通常メニュー表示)
スマートフォンではハンバーガーメニュー、PC 幅(768px 以上など)では通常の横並びナビゲーションに切り替える CSS です。
/* ヘッダーの基本レイアウト */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 120;
}
/* PC幅: 768px以上 */
@media (min-width: 768px) {
/* ハンバーガーボタンを非表示 */
.hamburger {
display: none;
}
/* オーバーレイを非表示 */
.overlay {
display: none;
}
/* ナビメニューを通常表示 */
.nav-menu {
position: static;
width: auto;
height: auto;
background: none;
box-shadow: none;
transform: none;
transition: none;
padding: 0;
overflow: visible;
}
/* リストを横並びに */
.nav-list {
display: flex;
gap: 24px;
}
.nav-list li {
border-bottom: none;
}
}
md:、Bootstrap では navbar-expand-md に相当します。フォーカストラップでキーボード操作を改善する
メニューが開いている間、Tab キーでのフォーカス移動がメニュー内に閉じ込められるようにする「フォーカストラップ」を実装すると、キーボードユーザーの操作性が大幅に向上します。
function trapFocus(menuElement) {
const focusableElements = menuElement.querySelectorAll(
'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
menuElement.addEventListener("keydown", (e) => {
if (e.key !== "Tab") return;
if (e.shiftKey) {
// Shift+Tab: 最初の要素から戻ったら最後にループ
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
// Tab: 最後の要素から進んだら最初にループ
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
});
}
// メニューを開いたときにフォーカストラップを有効化
// trapFocus(navMenu);
完成コードの全体像
ここまでの HTML・CSS・JavaScript をまとめた構成です。
| ファイル | 役割 |
|---|---|
| HTML | ボタン(aria 属性付き)・ナビゲーション・オーバーレイの 3 要素 |
| CSS | 三本線→×アニメーション・スライドイン・オーバーレイ・レスポンシブ |
| JavaScript | 開閉トグル・オーバーレイクリック・リンククリック・Esc キー・aria 更新・スクロール制御 |
ライブラリを使わず約 50 行の JavaScript で、実務レベルのハンバーガーメニューが完成します。
関連記事
- アコーディオンの作り方 — CSS アニメーション・アクセシビリティ対応
- classList の使い方完全ガイド — add・remove・toggle でクラスを操作
- スムーススクロールを実装する方法 — ページ内リンクの滑らかなスクロール
- 上向きスクロールで表示されるヘッダーの作り方
- ドロップダウンメニューを作る方法
- CSS 追従ヘッダーの作り方 — fixed・sticky の実装パターン
よくある質問
span 3 本を描画するのがベストです。アニメーション不要であれば SVG アイコンの方がサイズ調整やカラー変更が容易です。Unicode の「☰」は端末によって見た目が変わるためおすすめしません。document.body.style.overflow = "hidden" を設定し、閉じるときに "" に戻してください。iOS Safari では position: fixed も併用しないとスクロールが止まらない場合があります。aria-expanded がないと、スクリーンリーダーのユーザーはメニューが開いているのか閉じているのか判別できません。aria-controls と合わせて設定することで、ボタンとメニューの関連性も伝えられます。transition プロパティが正しく設定されているか確認してください。display: none から display: block への切り替えはアニメーションできません。transform・opacity・visibility の組み合わせでアニメーションさせるのが定石です。まとめ
ハンバーガーメニューの実装は、HTML・CSS・JavaScript の基本知識があればライブラリなしで構築できます。実装のポイントは以下の 4 つです。
- 三本線→×の CSS アニメーション: transform の rotate と translateY で実現
- スライドインのトランジション: transform: translateX で画面外から滑り込ませる
- 閉じるトリガーの充実: ボタン・オーバーレイ・リンクタップ・Esc キーの 4 パターン
- アクセシビリティ: aria-expanded・aria-controls・フォーカストラップで全ユーザーが操作可能に
これらを押さえておけば、どのサイトにも適用できる実用的なハンバーガーメニューが完成します。