スマートフォンやタブレットなど、画面の幅が狭い端末では、ナビゲーションメニューをコンパクトにまとめる工夫が必要です。そこで活躍するのが ハンバーガーメニュー です。
ハンバーガーメニューとは?
「☰」のような三本線のアイコンをタップすると、画面の端からメニューがスライドして現れます。
もう一度タップしたり、画面外をクリックするとメニューが閉じるなど、直感的に操作できて省スペースなデザインです。
1.基本機能:クリックでメニューを開閉する
まずは、下記のようなクリックするとメニューが表示/非表示になる、一番簡単なハンバーガーメニューを実装してみましょう。
See the Pen ハンバーガーメニュー01 by Yoshiko Nakamura (@Yoshiko-Nakamura) on CodePen.
HTML
<div class="hamburger">
☰
</div>
<nav class="menu">
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
CSS
.hamburger {
font-size: 32px;
cursor: pointer;
}
.menu {
display: none; /* 最初は非表示 */
}
.menu.active {
display: block; /* アクティブ状態で表示 */
}
JavaScript
const hamburger = document.querySelector('.hamburger');
const menu = document.querySelector('.menu');
hamburger.addEventListener('click', () => {
menu.classList.toggle('active');
});
上記のメソッドについては下記のページを参照しましょう


2.メニューをスライド表示する(CSSアニメーション)
1のメニューだとdisplay:noneとdisplay:blockのみで行っているので、「パッ」と切り替わります。UX的にも「スーッ」とスライドして出てくる方が自然でスマートです。次は、CSSの transform と transition を使って、右からスライドインするように改良してみましょう。
HTMLとJavaScriptには変更はありません。
See the Pen ハンバーガーメニュー02 by Yoshiko Nakamura (@Yoshiko-Nakamura) on CodePen.
CSS
右から左にスライドインします。translateX(100%)をtranslateX(-100%)に変更すると左から右にスライドインします。
.hamburger {
font-size: 32px;
cursor: pointer;
}
.menu {
transform: translateX(100%); /* 初期位置を右外に設定 */
transition: transform 0.5s ease; /* アニメーション効果 */
background-color: #ddd; /* 背景色 */
position: fixed; /* 画面全体に固定 */
top: 0; /* 上端 */
right: 0; /* 右端 */
width: 250px; /* メニュー幅 */
height: 100%; /* 全画面の高さ */
}
.menu.active {
transform: translateX(0); /* 画面内にスライド */
}

3.メニュー外をクリックしたら閉じる
UX(使いやすさ)の観点から、メニューが開いているときに画面の他の部分をタップしても閉じられるようにしておくと親切です。
HTML/CSSには変更はありませんのでJavaSriptのみ変更します。
See the Pen ハンバーガーメニュー03 by Yoshiko Nakamura (@Yoshiko-Nakamura) on CodePen.
JavaScript
// 必要な要素を最初に一度だけ取得
const hamburger = document.querySelector('.hamburger');
const menu = document.querySelector('.menu');
// ハンバーガーアイコンのクリックイベント
hamburger.addEventListener('click', () => {
menu.classList.toggle('active');
});
// メニュー外をクリックした場合の閉じる処理
document.addEventListener('click', (e) => {
if (!menu.contains(e.target) && !hamburger.contains(e.target)) {
menu.classList.remove('active');
}
});
この処理は「全体」に対してクリックイベントを追加し、クリックした要素が .menu や .hamburger の中に含まれていなければ、メニューを閉じます。
if (!menu.contains(e.target) && !hamburger.contains(e.target)) {
menu.classList.remove('active');
}
この部分のコードは、「メニューが開いているときに、画面のどこかをクリックしたら閉じる」処理です。
e.targetは、クリックされた要素(タグ)を指します。menu.contains(e.target)は、「クリックした要素が.menuの中に含まれているかどうか」を判定します。hamburger.contains(e.target)も同様に、ハンバーガーアイコンの中をクリックしたかどうかを確認しています。
つまりこの if 文は、「クリックされた場所が .menu の中でもなく、.hamburger の中でもない」=「外側をクリックした」ということを意味しています。その場合に
menu.classList.remove('active');
でメニューを閉じる、という流れです。

4.ハンバーガーアイコンを「×」に変える
ハンバーガーアイコン(3本線)が、メニューを開いたときに「×」に変化すると、
「閉じる動作」がより直感的に伝わります。ここでは、CSSとJavaScriptを組み合わせて、見た目を変化させましょう。今回はHTML/CSS/JavaScript全て変更します。
See the Pen ハンバーガー by Yoshiko Nakamura (@Yoshiko-Nakamura) on CodePen.
HTML
<div class="openbtn" id="hamburger">
<span></span>
<span></span>
<span></span>
</div>
<nav class="menu" id="menu">
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
CSS
/* ボタン外側 */
.openbtn {
position: relative;
background: #000;
cursor: pointer;
width: 50px;
height: 50px;
border-radius: 5px;
}
/* ボタンの中の線(3本) */
.openbtn span {
display: inline-block;
transition: all 0.4s;
position: absolute;
left: 14px;
height: 3px;
border-radius: 2px;
background: #fff;
width: 45%;
}
.openbtn span:nth-of-type(1) {
top: 15px;
}
.openbtn span:nth-of-type(2) {
top: 23px;
}
.openbtn span:nth-of-type(3) {
top: 31px;
}
/* アクティブ状態(×マーク) */
.openbtn.active span:nth-of-type(1) {
top: 18px;
left: 18px;
transform: translateY(6px) rotate(-45deg);
width: 30%;
}
.openbtn.active span:nth-of-type(2) {
opacity: 0;
}
.openbtn.active span:nth-of-type(3) {
top: 30px;
left: 18px;
transform: translateY(-6px) rotate(45deg);
width: 30%;
}
/* メニュー(前のまま) */
.menu {
transform: translateX(100%);
transition: transform 0.5s ease;
background-color: #ddd;
position: fixed;
top: 0;
right: 0;
width: 250px;
height: 100%;
}
.menu.active {
transform: translateX(0);
}
アイコンが「×」に変わる仕組み
.openbtn に .active クラスが付くと、CSSで3本の線の位置や角度が変わり、バツ印の形になります。
transform: rotate(...) を使って線を回転させています。真ん中の線は opacity: 0 で見えなくなります。JavaScriptでは、クリックのたびに .active クラスを付けたり外したりして、アイコンとメニューの両方を制御しています。
JavaScript
const hamburger = document.getElementById('hamburger');
const menu = document.getElementById('menu');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active'); // アイコンを×に
menu.classList.toggle('active'); // メニューを表示
});
// メニュー外クリックで閉じる
document.addEventListener('click', (e) => {
if (!menu.contains(e.target) && !hamburger.contains(e.target)) {
hamburger.classList.remove('active');
menu.classList.remove('active');
}
});
アイコンを右に移動 重なり順を考える
メニューを右に移動すると必要になるのはz-indexです。また最前面にあるメニューの中のリンク先をクリックした時に、メニューが閉じるように考えます。
See the Pen アイコンを右に移動 重なり順を考えたハンバーガーメニュー by Yoshiko Nakamura (@Yoshiko-Nakamura) on CodePen.
HTML
<div class="openbtn" id="hamburger">
<span></span>
<span></span>
<span></span>
</div>
<nav class="menu" id="menu">
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
CSS
/* ボタン外側 */
.openbtn {
position: fixed;
top: 10px;
right: 10px;
background: #57a2c7;
cursor: pointer;
width: 50px;
height: 50px;
border-radius: 5px;
z-index: 200;
}
/* ボタンの中の線(3本) */
.openbtn span {
display: inline-block;
transition: all 0.4s;
position: absolute;
left: 14px;
height: 3px;
border-radius: 2px;
background: #fff;
width: 45%;
}
.openbtn span:nth-of-type(1) {
top: 15px;
}
.openbtn span:nth-of-type(2) {
top: 23px;
}
.openbtn span:nth-of-type(3) {
top: 31px;
}
/* アクティブ状態(×マーク) */
.openbtn.active span:nth-of-type(1) {
top: 18px;
left: 18px;
transform: translateY(6px) rotate(-45deg);
width: 30%;
}
.openbtn.active span:nth-of-type(2) {
opacity: 0;
}
.openbtn.active span:nth-of-type(3) {
top: 30px;
left: 18px;
transform: translateY(-6px) rotate(45deg);
width: 30%;
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
}
/* メニュー(前のまま) */
.menu {
transform: translateX(100%);
transition: transform 0.5s ease;
background-color: #ddd;
position: fixed;
top: 0;
right: 0;
width: 100%;
height: 100%;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
}
.menu.active {
transform: translateX(0);
}
JavaScript
const hamburger = document.getElementById('hamburger');
const menu = document.getElementById('menu');
const menuLinks = document.querySelectorAll('#menu a');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active'); // アイコンを×に
menu.classList.toggle('active'); // メニューを表示
});
for (let i = 0; i < menuLinks.length; i++) {
menuLinks[i].addEventListener('click', () => {
hamburger.classList.remove('active');
menu.classList.remove('active');
});
}
querySelectorAll の戻り値は「NodeList」
const menuLinks = document.querySelectorAll('#menu a');
このコードで menuLinks には、<nav id="menu"> の中にある <a> 要素すべてが取得されます。このとき返されるのは「NodeList(ノードリスト)」という、配列のように使えるオブジェクトです。
NodeListとは?
menuLinks[0],menuLinks[1]のようにインデックス番号でアクセスできます。.lengthで要素の数が取得できます。- ただし、完全な配列(Array)ではないため、
map()やfilter()などの配列専用メソッドは使えません(ただしforEach()は使える)。
for 文でループして1つずつ取り出す
NodeList はインデックスでアクセスできるので、通常の for 文でループできます:
for (let i = 0; i < menuLinks.length; i++) {
// menuLinks[i] がそれぞれの a 要素
menuLinks[i].addEventListener('click', () => {
hamburger.classList.remove('active');
menu.classList.remove('active');
});
}
i = 0から始まり、menuLinks.length未満の間ループします。menuLinks[i]で対象の<a>要素を1つ取り出します。- その
<a>にclickイベントリスナーを追加します。 - ユーザーがクリックしたとき、
hamburgerとmenuの.activeクラスを外します。
ワイヤーフレームに設定してみよう
DesigntoCodeで作成したワイヤーフレームにハンバーガーボタンを設置してみましょう。

HTMLコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Espresso Lane</title>
<link rel="stylesheet" href="https://unpkg.com/css-remedy@0.1.0/css/remedy.css">
<link rel="stylesheet" href="css/hamburger.css">
<link rel="stylesheet" href="css/style.css">
<script src="js/custom.js" defer></script>
</head>
<body id="top">
<header class="header">
<div class="header-container container">
<h1 class="header-title">Espresso Lane</h1>
<div class="openbtn" id="hamburger">
<span></span>
<span></span>
<span></span>
</div>
<nav class="header-nav header-menu" id="menu">
<ul class="header-list">
<li class="header-list-item"><a href="#top" class="header-link">Home</a></li>
<li class="header-list-item"><a href="#aboutSection" class="header-link">About Us</a></li>
<li class="header-list-item"><a href="#menuSection" class="header-link">Menu</a></li>
<li class="header-list-item"><a href="#accessSection" class="header-link">Access</a></li>
</ul>
</nav>
</div>
</header>
<figure class="keyvisual">
<img src="https://dummy.kobeya.com/?width=1440&height=600&text=keyvisual" width="1440" height="600"
alt="キービジュアルの説明文">
</figure>
<section id="aboutSection" class="about">
<div class="container about-container">
<h2 class="section-title">About Us</h2>
<p class="about-text">
Espresso
Laneは、小倉駅から徒歩2分の都会の真ん中にありながら、まるで別世界のように静かでリラックスできるカフェです。「おいしいコーヒーと心地よい空間で、疲れた心と体を癒したい。」そんな想いを込めて、私たちは日々お客様をお迎えしています。
</p>
</div>
</section>
<section id="menuSection" class="menu">
<div class="container menu-container">
<h2 class="section-title">Menu</h2>
<ul class="menu-list">
<li class="menu-item">
<article class="menu-article">
<img src="https://placehold.jp/590x357.png?text=image" class="menu-image" width="590"
height="357" alt="coffee">
<h3 class="menu-name">Coffee</h3>
<p class="menu-description">
一日の始まりにも、ちょっとした休憩にも。厳選されたコーヒー豆を使用し、丁寧に抽出した一杯は、香りと深い味わいで心を満たします。リラックスした時間をお楽しみください。</p>
</article>
</li>
<li class="menu-item">
<article class="menu-article">
<img src="https://placehold.jp/590x357.png?text=image" class="menu-image" width="590"
height="357" alt="モーニングセット">
<h3 class="menu-name">Morning Set</h3>
<p class="menu-description">焼きたてのクロワッサン、新鮮なサラダ、そして淹れたてのコーヒーで始まる特別な朝。</p>
</article>
</li>
<li class="menu-item">
<article class="menu-article">
<img src="https://placehold.jp/590x357.png?text=image" class="menu-image" width="590"
height="357" alt="ランチメニュー">
<h3 class="menu-name">Lunch Menu</h3>
<p class="menu-description">
都会の喧騒を忘れ、静かな店内で楽しむランチタイム。選べるパニーニやリゾットに日替わりスープを添えて、心も体も満たされるひとときをお過ごしください。</p>
</article>
</li>
<li class="menu-item">
<article class="menu-article">
<img src="https://placehold.jp/590x357.png?text=image" class="menu-image" width="590"
height="357" alt="デザート">
<h3 class="menu-name">Desserts</h3>
<p class="menu-description">
一日頑張った自分への甘いご褒美。自家製のチーズケーキやエクレア、濃厚なチョコレートタルトが、疲れた心を癒してくれます。コーヒーとの相性も抜群です。</p>
</article>
</li>
</ul>
</div>
</section>
<section id="accessSection" class="access">
<div class="container access-container">
<h2 class="section-title">Access</h2>
<div class="access-address">
<address>
<p class="access-info">〒123-4567 福岡県北九州市小倉北区XX-XXXX</p>
<p class="access-info">TEL: <span class="mobile-only"><a
href="tel:0930001234">093-000-1234</a></span>
<span class="desktop-only">093-000-1234</span>
</p>
</address>
<p class="access-info">営業時間 平日: 7:30〜20:00 土日祝: 9:00〜18:00</p>
</div>
<iframe class="access-map"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3312.104288212224!2d130.88000087553124!3d33.886967873219604!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3543bf4bf949501b%3A0x6918bbd9f307b06c!2z5bCP5YCJ6aeF!5e0!3m2!1sja!2sjp!4v1736306320341!5m2!1sja!2sjp"
width="1200" height="400" style="border:0;width:100%;" allowfullscreen="" loading="lazy"
referrerpolicy="no-referrer-when-downgrade"></iframe>
</div>
</section>
<footer class="footer">
<div id="backToTop">
<a href="#top">
▲
</a>
</div>
<div class="footer-container">
<p class="footer-title"><a href="#top">Espresso Lane</a></p>
<address class="footer-address">
<p class="footer-info">〒123-4567 福岡県北九州市小倉北区XX-XXXX</p>
<p class="access-info"></p>TEL: <span class="mobile-only"><a
href="tel:0930001234">093-000-1234</a></span>
<span class="desktop-only">093-000-1234</span></p>
</address>
<p class="footer-copyright">© 2025 Espresso Lane. All rights reserved.</p>
</div>
</footer>
</body>
</html>
CSS
ベースのCSS
:root {
--base-color: var(--white)fff;
--main-color: #441702;
--accent-color: #ff5722;
--text-color: #000000;
--white: #fff;
--gray: #ddd;
}
/* HTMLのデフォルト設定 */
html {
font-size: 100%;
box-sizing: border-box;
scroll-behavior: smooth;
}
*,
*::before,
*::after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
body {
font-family: sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text-color);
background-color: var(--base-color);
word-break: break-word;
}
/* リンクの基本スタイル */
a:link {
color: var(--main-color);
text-decoration: none;
}
a:hover {
color: var(--accent-color);
text-decoration: underline;
}
a:visited {
color: var(--main-color);
}
a:active,
a:focus {
color: var(--main-color);
}
/* スタイルリセット */
ul,
ol {
list-style: none;
}
address {
font-style: normal;
}
.container {
width: calc(100% - 2rem);
margin: 0 auto;
max-width: 1200px;
}
/* header */
.header {
padding: 10px 0;
text-align: center;
background-color: #ffe6a2;
}
.header-title {
font-size: 2rem;
/* margin-bottom: 20px; */
}
.header-list {
display: flex;
gap: 1rem;
justify-content: center;
align-items: center;
}
/* keyvisual */
.keyvisual {
width: 100%;
max-width: 1440px;
margin: 0 auto;
padding: 0;
}
.keyvisual img {
width: 100%;
height: 80vh;
object-fit: cover;
vertical-align: bottom;
}
/* セクション */
section {
padding: 30px 0;
}
/* 各セクション確認のため色をつけている */
.about {
background-color: #ffd9d9;
}
.menu {
background-color: #f9face;
}
.access {
background-color: #aaf4ff;
}
/* タイトルスタイル */
.section-title {
font-size: 1.5rem;
text-align: center;
color: var(--main-color);
padding-bottom: 0.5rem;
letter-spacing: 0.05em;
margin-bottom: 2rem;
}
/* about */
.about-text {
text-align: justify;
}
/* menu */
.menu-list {
display: grid;
grid-template-columns: 1fr;
row-gap: 2rem;
}
.menu-list img {
max-width: 100%;
width: 100%;
height: auto;
}
.menu-item {
border: 1px solid var(--gray);
height: 100%;
}
.menu-article {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--white);
padding-bottom: 20px;
}
.menu-image {
width: 100%;
height: auto;
object-fit: cover;
}
.menu-name {
text-align: center;
padding: 1rem 0 0.5rem;
}
.menu-description {
padding: 0 2rem;
margin-bottom: auto;
}
/* access */
.access-info {
padding: 0 1rem;
}
.access-map {
margin: 20px 0 0;
display: block;
max-width: 100%;
}
/* footer */
#backToTop a {
display: block;
position: fixed;
right: 10px;
bottom: 100px;
background-color: var(--main-color);
padding: .75rem 1rem;
color: var(--white);
text-decoration: none;
border-radius: 5px;
z-index: 200;
opacity: 0;
visibility: hidden;
transition: opacity 0.4s ease, visibility 0.4s ease;
}
#backToTop.show a {
opacity: 1;
visibility: visible;
}
.footer {
background-color: var(--main-color);
padding: 1rem 0;
color: var(--white);
text-align: center;
}
.footer-title a {
font-size: 1.5rem;
color: var(--white);
}
.footer-title a:hover {
color: var(--accent-color);
text-decoration: underline;
}
.footer-info {
font-size: 0.9rem;
}
.footer-copyright {
font-size: 0.7rem;
padding-top: 1rem;
}
/* モバイル専用表示 */
.mobile-only {
display: inline;
}
.desktop-only {
display: none;
}
/* デスクトップ向け拡張 */
@media (min-width: 768px) {
body {
font-size: 16px;
line-height: 1.8;
}
section {
padding: 100px 0;
}
.section-title {
font-size: 2rem;
}
.about-container {
width: 520px;
margin: 0 auto;
}
.menu-list {
grid-template-columns: repeat(2, 1fr);
gap: 4rem 2rem;
}
.access-address {
width: 540px;
margin: 0 auto 30px;
text-align: center;
}
.mobile-only {
display: none;
}
.desktop-only {
display: inline;
}
}
@media (min-width: 1024px) {
.openbtn {
display: none;
}
.header-title {
width: 50%;
}
.header-container {
display: flex;
align-items: center;
}
.header-menu {
display: block;
transform: none !important;
position: static;
height: auto;
background-color: transparent;
}
.header-menu .header-list {
flex-direction: row;
justify-content: flex-end;
height: auto;
}
.header-menu .header-link {
color: #441702;
padding: 1rem;
}
}
ハンバーガーメニュー用のCSS
.openbtn {
position: fixed;
top: 10px;
right: 10px;
background: transparent;
cursor: pointer;
width: 50px;
height: 50px;
border-radius: 5px;
z-index: 1000;
/* メニューよりも常に上にする */
}
/* ボタンの中の線(3本) */
.openbtn span {
display: inline-block;
transition: all 0.4s;
position: absolute;
left: 14px;
height: 3px;
border-radius: 2px;
background: #441702;
width: 45%;
}
.openbtn span:nth-of-type(1) {
top: 15px;
}
.openbtn span:nth-of-type(2) {
top: 23px;
}
.openbtn span:nth-of-type(3) {
top: 31px;
}
/* アクティブ状態(×マーク) */
.openbtn.active span:nth-of-type(1) {
top: 18px;
left: 18px;
transform: translateY(6px) rotate(-45deg);
width: 30%;
background: #fff;
}
.openbtn.active span:nth-of-type(2) {
opacity: 0;
}
.openbtn.active span:nth-of-type(3) {
top: 30px;
left: 18px;
transform: translateY(-6px) rotate(45deg);
width: 30%;
background: #fff;
}
.header-menu {
z-index: 900;
transform: translateX(100%);
opacity: 0;
pointer-events: none;
transition: transform 0.5s ease, opacity 0.5s ease;
background-color: #441702e0;
position: fixed;
top: 0;
right: 0;
width: 100vw;
height: 100vh;
}
.header-menu.active {
transform: translateX(0);
opacity: 1;
pointer-events: auto;
}
.header-menu .header-list {
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
}
.header-menu .header-link {
color: #fff;
}
JavaScript
// ハンバーガー
const hamburger = document.getElementById('hamburger');
const menu = document.getElementById('menu');
const menuLinks = document.querySelectorAll('#menu a');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active'); // アイコンを×に
menu.classList.toggle('active'); // メニューを表示
});
for (const link of menuLinks) {
link.addEventListener('click', () => {
hamburger.classList.remove('active');
menu.classList.remove('active');
});
}
// メニュー外クリックで閉じる
document.addEventListener('click', (e) => {
if (!menu.contains(e.target) && !hamburger.contains(e.target)) {
hamburger.classList.remove('active');
menu.classList.remove('active');
}
});
//BackToTop
const backToTop = document.getElementById('backToTop');
window.addEventListener('scroll', () => {
if (window.scrollY > 600) {
backToTop.classList.add('show');
} else {
backToTop.classList.remove('show');
}
})
