はじめに (対象読者・この記事でわかること)

この記事は、Webサイト制作を始めたばかりのプログラミング初学者の方や、JavaScriptを使ってインタラクティブなUIを実装したいと考えている方を対象にしています。特に、モバイルフレンドリーなWebサイトに不可欠な「ハンバーガーメニュー」のボタン実装に焦点を当てて解説します。

この記事を読むことで、ハンバーガーメニューのボタンをJavaScriptでどのように制御し、開閉アニメーションやアクセシビリティに対応させるか、その具体的な手順とコード例を学ぶことができます。これにより、見栄えが良く、かつ使いやすいナビゲーションメニューを実装できるようになるでしょう。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 * HTML/CSSの基本的な知識 (要素の構造、セレクタ、プロパティなど) * JavaScriptの基本的な構文 (変数、関数、イベントリスナー、DOM操作など)

ハンバーガーメニュー実装の重要性:なぜ今、きちんと学ぶべきか

現代のWebサイトにおいて、ハンバーガーメニューはスマートフォンやタブレットでの閲覧体験を向上させるために不可欠なUI要素です。限られた画面スペースに多くのナビゲーションリンクを効率的に配置し、ユーザーが必要な時にだけメニューを表示させることで、コンテンツへの集中を妨げずに情報を提供できます。

しかし、単に開閉するだけでなく、ユーザーにとって使いやすいメニューであるためにはいくつかの考慮が必要です。例えば、開閉時のスムーズなアニメーション、キーボード操作やスクリーンリーダーにも対応したアクセシビリティ、そして予期せぬスクロールを防ぐ工夫など、実装には細やかな配慮が求められます。このセクションでは、これらのポイントを抑えながら、なぜハンバーガーメニューの正しい実装が重要なのか、その背景とメリットを解説します。

JavaScriptで実装する!ハンバーガーメニューボタンのステップバイステップ

それでは、実際にJavaScriptを使ってハンバーガーメニューの開閉機能を実装する手順を見ていきましょう。今回は、ボタンクリックでメニューが開閉し、同時にボタン自体もアニメーションするシンプルなハンバーガーメニューを作成します。

ステップ1: HTML構造とCSSの準備

まずは、ハンバーガーメニューボタンとメニュー本体の基本的なHTML構造と、それらをスタイリングするためのCSSを準備します。

HTML:

Html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ハンバーガーメニュー</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <button class="hamburger-button" aria-controls="main-menu" aria-expanded="false" aria-label="メニューを開閉"> <span class="hamburger-icon"></span> <span class="hamburger-icon"></span> <span class="hamburger-icon"></span> </button> <nav id="main-menu" class="main-navigation" aria-hidden="true"> <ul> <li><a href="#">ホーム</a></li> <li><a href="#">サービス</a></li> <li><a href="#">会社概要</a></li> <li><a href="#">お問い合わせ</a></li> </ul> </nav> </header> <main style="height: 200vh; background-color: #f0f0f0;"> <p>ここにメインコンテンツが入ります。スクロールしてメニューの動作を確認してください。</p> </main> <script src="script.js"></script> </body> </html>

CSS (style.css):

Css
/* リセット・共通スタイル */ body { margin: 0; font-family: Arial, sans-serif; overflow-x: hidden; /* 横スクロール禁止 */ } /* ヘッダー */ header { background-color: #333; color: white; padding: 1rem; display: flex; justify-content: flex-end; /* ボタンを右寄せ */ align-items: center; position: relative; /* メニューの配置基準 */ z-index: 1000; } /* ハンバーガーボタン */ .hamburger-button { background: none; border: none; cursor: pointer; display: flex; flex-direction: column; justify-content: space-around; width: 30px; height: 24px; padding: 0; position: relative; z-index: 1001; /* メニューより手前に */ } .hamburger-icon { display: block; width: 100%; height: 3px; background-color: white; transition: all 0.3s ease; /* アニメーション用 */ border-radius: 2px; } /* ボタンが開いた時のアニメーション */ .hamburger-button.is-open .hamburger-icon:nth-child(1) { transform: translateY(10.5px) rotate(45deg); } .hamburger-button.is-open .hamburger-icon:nth-child(2) { opacity: 0; } .hamburger-button.is-open .hamburger-icon:nth-child(3) { transform: translateY(-10.5px) rotate(-45deg); } /* メインナビゲーション */ .main-navigation { position: fixed; top: 0; right: -100%; /* 初期状態は画面外 */ width: 250px; height: 100%; background-color: #444; color: white; transition: right 0.3s ease-in-out; /* スライドアニメーション */ padding-top: 60px; /* ヘッダーの高さ分 */ box-shadow: -2px 0 5px rgba(0,0,0,0.5); z-index: 999; } .main-navigation.is-open { right: 0; /* 開いた時に表示 */ } .main-navigation ul { list-style: none; padding: 0; margin: 0; } .main-navigation li a { display: block; padding: 15px 20px; text-decoration: none; color: white; border-bottom: 1px solid #555; transition: background-color 0.2s ease; } .main-navigation li a:hover { background-color: #555; } /* メニュー開閉時のbodyスクロール制御 */ body.no-scroll { overflow: hidden; }

解説: * HTML: * <button class="hamburger-button"> をメニュー開閉のトリガーとします。内部に3つの<span>タグを配置し、CSSでハンバーガーアイコンを表現します。 * アクセシビリティのため、aria-controls (メニューのID)、aria-expanded (メニューの状態)、aria-label (ボタンの機能説明) 属性を付与します。 * <nav id="main-menu" class="main-navigation"> が実際のメニューコンテンツです。 * メニュー本体にもaria-hidden="true"を設定し、初期状態ではスクリーンリーダーから隠します。 * CSS: * .hamburger-button のアイコン (.hamburger-icon) は、is-openクラスが付与された際に transform プロパティを使ってX字に変化するように設定しています。 * .main-navigationposition: fixed で画面の右端外側 (right: -100%) に配置し、transition プロパティでスライドアニメーションを可能にしています。 * is-open クラスが .main-navigation に付与されると right: 0 となり、画面内に表示されます。 * body.no-scroll クラスは、メニューが開いたときに背景のスクロールを無効にするためのものです。

ステップ2: JavaScriptによる開閉機能の実装

次に、ボタンのクリックイベントを検知し、HTML要素のクラスを操作してメニューの表示・非表示を切り替えるJavaScriptを記述します。

JavaScript (script.js):

Javascript
document.addEventListener('DOMContentLoaded', () => { const hamburgerButton = document.querySelector('.hamburger-button'); const mainNavigation = document.getElementById('main-menu'); const body = document.body; // ボタンが存在するかチェック if (!hamburgerButton || !mainNavigation) { console.error('ハンバーガーボタンまたはナビゲーションメニューが見つかりません。'); return; } hamburgerButton.addEventListener('click', () => { // ボタンとメニューに'is-open'クラスをトグル hamburgerButton.classList.toggle('is-open'); mainNavigation.classList.toggle('is-open'); // bodyに'no-scroll'クラスをトグルして背景のスクロールを制御 body.classList.toggle('no-scroll'); // アクセシビリティ属性の更新 const isExpanded = hamburgerButton.classList.contains('is-open'); hamburgerButton.setAttribute('aria-expanded', isExpanded); mainNavigation.setAttribute('aria-hidden', !isExpanded); }); });

解説: 1. DOMContentLoaded イベントリスナー: HTMLのDOMツリーが完全に読み込まれてからスクリプトを実行するようにします。これにより、JavaScriptが要素を取得する前にHTMLが準備されていないというエラーを防げます。 2. 要素の取得: document.querySelector()document.getElementById() を使って、ハンバーガーボタン、メニュー本体、そしてbody要素への参照を取得します。 3. イベントリスナーの追加: hamburgerButton.addEventListener('click', ...) で、ボタンがクリックされたときに実行される関数を登録します。 4. クラスのトグル: * hamburgerButton.classList.toggle('is-open'): ボタン自体に is-open クラスを付け外しします。これにより、CSSで定義したボタンのアニメーションが適用されます。 * mainNavigation.classList.toggle('is-open'): メニュー本体にも is-open クラスを付け外しします。これにより、メニューが画面内にスライドして表示/非表示されます。 * body.classList.toggle('no-scroll'): メニューが開いている間、背景がスクロールしないように body 要素に no-scroll クラスを適用します。これはモバイルでのユーザー体験を向上させます。 5. アクセシビリティ属性の更新: * hamburgerButton.setAttribute('aria-expanded', isExpanded): スクリーンリーダーに対して、メニューが開いているか閉じているかを伝えます (trueまたはfalse)。 * mainNavigation.setAttribute('aria-hidden', !isExpanded): メニューが閉じているときはスクリーンリーダーからメニューコンテンツを隠し、開いているときは見えるようにします。

ハマった点やエラー解決

実装中に遭遇しやすい典型的な問題とその解決策です。

1. CSSアニメーションが効かない、またはぎこちない

  • 問題: メニューが開閉する際にアニメーションがスムーズでない、または全くアニメーションしない。
  • 原因:
    • display: none;display: block; の切り替えを使用している場合。displayプロパティはアニメーションできないため、即座に表示/非表示が切り替わってしまいます。
    • transition プロパティの設定漏れ、または対象プロパティが正しくない。
    • 初期状態と目標状態のプロパティが異なる場所(例:opacity: 0;opacity: 1;)で定義されていない。
  • 解決策:
    • display: none; の代わりに、visibility: hidden;opacity: 0;、または transform: translateX(-100%); のように、アニメーション可能なプロパティを使用し、これらを transition で切り替えるようにします。
    • transition: all 0.3s ease; のように、対象となるCSSプロパティ(例: right, opacity, transform)に適切な transition を設定します。

2. メニューが開いたときに背景がスクロールしてしまう

  • 問題: ハンバーガーメニューが開いている間に、その下のコンテンツがスクロールできてしまう。特にモバイルで顕著。
  • 原因: body要素のスクロールが制限されていないため。
  • 解決策:
    • メニューが開いた際にbody要素にoverflow: hidden;を設定するクラス(例: no-scroll)を追加し、閉じた際にそのクラスを削除します。
    • CSS: css body.no-scroll { overflow: hidden; }
    • JavaScript: body.classList.toggle('no-scroll'); を追加します。

3. アクセシビリティ対応の不足

  • 問題: スクリーンリーダーを使用しているユーザーがメニューの状態を認識できない、またはキーボード操作でメニューを操作できない。
  • 原因: aria属性の未設定、またはJavaScriptでの状態更新漏れ。
  • 解決策:
    • aria-expanded: ハンバーガーボタンにaria-expanded="false" (閉鎖状態) を初期設定し、JavaScriptで開閉時にtrue/falseを切り替えます。
    • aria-controls: ハンバーガーボタンにaria-controls="[メニューのID]"を設定し、どの要素を操作するボタンなのかを明示します。
    • aria-hidden: メニュー本体にaria-hidden="true" (非表示状態) を初期設定し、JavaScriptで開閉時にtrue/falseを切り替えることで、スクリーンリーダーが不要なコンテンツを読み上げないようにします。
    • キーボードナビゲーション: メニュー項目は<a>タグを使用することで、デフォルトでTabキーによるフォーカス移動に対応します。メニューが開いた際に最初の項目にフォーカスを当てるなど、より高度な対応も検討すると良いでしょう。

まとめ

本記事では、JavaScriptを使用してWebサイトにハンバーガーメニューボタンを実装する方法を詳細に解説しました。

  • HTML/CSSによる基礎構造: ハンバーガーアイコンとメニュー本体のHTML構造を定義し、CSSで初期状態と開閉時のスタイル、アニメーションを設定しました。
  • JavaScriptによるインタラクティブな制御: ボタンのクリックイベントを検知し、classList.toggle()を使ってクラスを切り替えることで、メニューの開閉機能を実現しました。
  • アクセシビリティとユーザー体験の向上: aria-expandedaria-hiddenといったARIA属性の利用、そしてメニュー開閉時の背景スクロール制御を通じて、より多くのユーザーにとって使いやすいメニューにすることの重要性を学びました。

この記事を通して、単にメニューを開閉させるだけでなく、スムーズなアニメーションやアクセシビリティにも配慮した、質の高いハンバーガーメニューを実装するスキルを習得できたことでしょう。

今後は、メニュー外クリックで閉じる機能、ESCキーでの閉じる機能、フォーカス管理、またはReactやVue.jsのようなフレームワークでの実装についても記事にする予定です。

参考資料