【JavaScript】ハンバーガーメニューの実装方法|CSS アニメーション・オーバーレイ・アクセシビリティ・レスポンシブ対応まで解説

スマートフォンやタブレットでの閲覧が当たり前になった現在、限られた画面幅でナビゲーションをコンパクトにまとめるハンバーガーメニューはほぼすべてのサイトで採用されています。この記事では、HTML・CSS・JavaScript だけでライブラリを使わずに実装する方法を、アニメーション・アクセシビリティ・レスポンシブ対応まで含めて解説します。

この記事でわかること
・ハンバーガーメニューの HTML・CSS・JavaScript 基本構成
・三本線アイコンの CSS 描画と ×(閉じる)アニメーション
・スライドインのトランジション
・オーバーレイ(背景暗転)の実装
・メニュー外クリック・Esc キーで閉じる処理
・aria-expanded / aria-controls のアクセシビリティ対応
・レスポンシブで PC 幅では通常メニューに切り替える方法
スポンサーリンク

HTML の構成

ハンバーガーメニューの HTML は「ボタン」「ナビゲーション」「オーバーレイ」の 3 要素で構成します。

HTML
<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 で描くと、×印への変形アニメーションが実現できます。

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° 回転すると ×(閉じる)アイコンになります。

CSS
/* メニューが開いた時: 三本線 → × に変形 */
.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) でスライドインさせます。

CSS
/* ナビゲーションメニュー */
.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

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 でメニューの開閉を制御する

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 イベントで制御します。

JavaScript(オーバーレイなしの場合)
document.addEventListener("click", (e) => {
  // メニューまたはハンバーガーボタンの内部であれば何もしない
  if (navMenu.contains(e.target) || hamburger.contains(e.target)) {
    return;
  }
  // それ以外の場所がクリックされたらメニューを閉じる
  closeMenu();
});
contains(e.target) を使って、クリックされた要素がメニューの内部かどうかを判定します。この判定なしだと、メニュー内のリンクをクリックした瞬間にメニューが閉じてしまいます。

レスポンシブ対応(PC 幅では通常メニュー表示)

スマートフォンではハンバーガーメニュー、PC 幅(768px 以上など)では通常の横並びナビゲーションに切り替える CSS です。

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;
  }
}
ブレイクポイント(768px)はサイトのデザインに合わせて調整してください。Tailwind CSS では md:、Bootstrap では navbar-expand-md に相当します。

フォーカストラップでキーボード操作を改善する

メニューが開いている間、Tab キーでのフォーカス移動がメニュー内に閉じ込められるようにする「フォーカストラップ」を実装すると、キーボードユーザーの操作性が大幅に向上します。

JavaScript
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);
フォーカストラップは WAI-ARIA のダイアログパターンに基づくベストプラクティスです。モーダルやドロワーメニューでは積極的に導入しましょう。

完成コードの全体像

ここまでの HTML・CSS・JavaScript をまとめた構成です。

ファイル 役割
HTML ボタン(aria 属性付き)・ナビゲーション・オーバーレイの 3 要素
CSS 三本線→×アニメーション・スライドイン・オーバーレイ・レスポンシブ
JavaScript 開閉トグル・オーバーレイクリック・リンククリック・Esc キー・aria 更新・スクロール制御

ライブラリを使わず約 50 行の JavaScript で、実務レベルのハンバーガーメニューが完成します。

関連記事

よくある質問

Qハンバーガーメニューのアイコンは CSS で作るべきですか?それとも SVG やアイコンフォントを使うべきですか?
A×印への変形アニメーションが必要なら CSS で span 3 本を描画するのがベストです。アニメーション不要であれば SVG アイコンの方がサイズ調整やカラー変更が容易です。Unicode の「☰」は端末によって見た目が変わるためおすすめしません。
Qメニューを開いたときに背景がスクロールしてしまいます。
Aメニュー展開時に document.body.style.overflow = "hidden" を設定し、閉じるときに "" に戻してください。iOS Safari では position: fixed も併用しないとスクロールが止まらない場合があります。
Qaria-expanded は本当に必要ですか?
Aはい。aria-expanded がないと、スクリーンリーダーのユーザーはメニューが開いているのか閉じているのか判別できません。aria-controls と合わせて設定することで、ボタンとメニューの関連性も伝えられます。
QjQuery を使わずに実装するメリットは?
Aファイルサイズの削減(jQuery は約 90KB)とページ読み込み速度の向上が最大のメリットです。ハンバーガーメニュー程度の DOM 操作なら Vanilla JavaScript で十分に実装でき、保守性も問題ありません。
Qハンバーガーメニューのアニメーションが滑らかに動きません。
Atransition プロパティが正しく設定されているか確認してください。display: none から display: block への切り替えはアニメーションできません。transformopacityvisibility の組み合わせでアニメーションさせるのが定石です。

まとめ

ハンバーガーメニューの実装は、HTML・CSS・JavaScript の基本知識があればライブラリなしで構築できます。実装のポイントは以下の 4 つです。

  • 三本線→×の CSS アニメーション: transform の rotate と translateY で実現
  • スライドインのトランジション: transform: translateX で画面外から滑り込ませる
  • 閉じるトリガーの充実: ボタン・オーバーレイ・リンクタップ・Esc キーの 4 パターン
  • アクセシビリティ: aria-expanded・aria-controls・フォーカストラップで全ユーザーが操作可能に

これらを押さえておけば、どのサイトにも適用できる実用的なハンバーガーメニューが完成します。