【CSS】ハンバーガーメニュー、開いたあとの見せ方4選とスマホで使いやすくするコツ



前に書いた【CSS】ハンバーガーメニュー実装サンプル集では、ボタンの動きと、横から出るメニューを紹介しました。
今回はその続きです。
ボタンを押したあと、メニューをどう見せるか。
全画面に出す、上からおろす、まるく広げる、下から出す。見せ方を変えるだけで、サイトの印象はずいぶん変わります。
後半では、スマホで「押しにくい」「閉じかたが分からない」とならないための、5つのコツもまとめました。
どの見本も jQuery を使わず、素の JavaScript で動きます。
全画面にふわっと出す
ボタンを押すと、画面いっぱいにメニューがふわっと出てきます。項目が、上から順に少しずつ浮かび上がります。
項目が少ないサイトや、写真の多いサイトによく合います。
コードを見る(HTML・CSS・JavaScript)
HTML
<div id="navArea">
<nav>
<ul>
<li><a href="#"><span>01</span>デザインのメモ</a></li>
<li><a href="#"><span>02</span>works</a></li>
<li><a href="#"><span>03</span>service</a></li>
<li><a href="#"><span>04</span>contact</a></li>
</ul>
</nav>
<button class="toggle_btn" aria-label="メニュー" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
CSS
/* ===== 全画面にふわっと出すタイプ ===== */
/* メニュー(画面いっぱい。ふわっと出る) */
#navArea nav {
position: fixed;
inset: 0;
z-index: 3;
display: grid;
place-items: center;
padding: 96px 32px 32px;
background: #2b2a27;
color: #fff;
opacity: 0;
visibility: hidden;
transition: .5s;
}
#navArea.open nav { opacity: 1; visibility: visible; }
#navArea nav ul { width: min(360px, 100%); }
#navArea nav a { display: flex; align-items: baseline; gap: 16px; padding: 16px 0; font-size: 20px; }
#navArea nav a span { font-size: 12px; color: #e8dc6a; }
/* 項目を、下から順に */
#navArea nav li {
border-bottom: 1px solid rgba(255, 255, 255, .16);
opacity: 0;
transform: translateY(16px);
transition: .5s;
}
#navArea.open nav li { opacity: 1; transform: translateY(0); }
#navArea.open nav li:nth-child(1) { transition-delay: .1s; }
#navArea.open nav li:nth-child(2) { transition-delay: .2s; }
#navArea.open nav li:nth-child(3) { transition-delay: .3s; }
#navArea.open nav li:nth-child(4) { transition-delay: .4s; }
/* ボタン */
.toggle_btn {
position: fixed;
top: 16px;
right: 16px;
z-index: 4;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
background: #e8dc6a;
cursor: pointer;
}
.toggle_btn span {
position: absolute;
left: 14px;
width: 20px;
height: 2px;
border-radius: 2px;
background: #2b2a27;
transition: .5s;
}
.toggle_btn span:nth-child(1) { top: 17px; }
.toggle_btn span:nth-child(2) { top: 23px; }
.toggle_btn span:nth-child(3) { top: 29px; }
/* 開いたら × に */
#navArea.open .toggle_btn span:nth-child(1) { transform: translateY(6px) rotate(-45deg); }
#navArea.open .toggle_btn span:nth-child(2) { opacity: 0; }
#navArea.open .toggle_btn span:nth-child(3) { transform: translateY(-6px) rotate(45deg); }
@media (max-width: 600px) {
#navArea nav a { font-size: 18px; }
}
@media (prefers-reduced-motion: reduce) {
#navArea * { transition: none !important; }
}
JavaScript
const nav = document.getElementById('navArea');
const btn = nav.querySelector('.toggle_btn');
btn.addEventListener('click', () => {
const isOpen = nav.classList.toggle('open');
btn.setAttribute('aria-expanded', isOpen);
});
項目の出かたは、li ごとに transition-delay を 0.1 秒ずつずらしているだけです。
閉じるときは .open が外れて delay もなくなるので、全部が一緒に消えます。
上からおりてくる
ヘッダーの上から、メニューがすっとおりてきます。PC では横に、スマホでは縦に並びます。
ページの中身を隠しすぎないので、「メニューを見ながら、どこへ行くか考える」のに向いています。
コードを見る(HTML・CSS・JavaScript)
HTML
<div id="navArea">
<nav>
<ul>
<li><a href="#"><span>01</span>デザインのメモ</a></li>
<li><a href="#"><span>02</span>works</a></li>
<li><a href="#"><span>03</span>service</a></li>
<li><a href="#"><span>04</span>contact</a></li>
</ul>
</nav>
<button class="toggle_btn" aria-label="メニュー" aria-expanded="false">
<span></span><span></span><span></span>
</button>
<div id="mask"></div>
</div>
CSS
/* ===== 上からおりてくるタイプ ===== */
/* メニュー(上からおりる) */
#navArea nav {
position: fixed;
top: 0;
left: 0;
z-index: 3;
width: 100%;
padding: 80px 24px 24px;
background: #f3efe6;
transform: translateY(-100%);
visibility: hidden;
transition: .5s;
}
#navArea.open nav { transform: translateY(0); visibility: visible; }
#navArea nav ul {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
max-width: 880px;
margin: 0 auto;
}
#navArea nav li { border-bottom: 1px solid #e8e3d8; }
#navArea nav a { display: flex; align-items: baseline; gap: 16px; padding: 16px 8px; }
#navArea nav a span { font-size: 12px; color: #6b675f; }
/* うしろを暗くする */
#mask {
position: fixed;
inset: 0;
z-index: 2;
background: rgba(43, 42, 39, .5);
opacity: 0;
visibility: hidden;
transition: .5s;
}
#navArea.open #mask { opacity: 1; visibility: visible; }
/* ボタン */
.toggle_btn {
position: fixed;
top: 16px;
right: 16px;
z-index: 4;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
background: #2b2a27;
cursor: pointer;
}
.toggle_btn span {
position: absolute;
left: 14px;
width: 20px;
height: 2px;
border-radius: 2px;
background: #fff;
transition: .5s;
}
.toggle_btn span:nth-child(1) { top: 17px; }
.toggle_btn span:nth-child(2) { top: 23px; }
.toggle_btn span:nth-child(3) { top: 29px; }
/* 開いたら × に */
#navArea.open .toggle_btn span:nth-child(1) { transform: translateY(6px) rotate(-45deg); }
#navArea.open .toggle_btn span:nth-child(2) { opacity: 0; }
#navArea.open .toggle_btn span:nth-child(3) { transform: translateY(-6px) rotate(45deg); }
@media (max-width: 600px) {
#navArea nav { padding: 72px 16px 16px; }
}
@media (prefers-reduced-motion: reduce) {
#navArea * { transition: none !important; }
}
JavaScript
const nav = document.getElementById('navArea');
const btn = nav.querySelector('.toggle_btn');
const mask = document.getElementById('mask');
btn.addEventListener('click', () => {
const isOpen = nav.classList.toggle('open');
btn.setAttribute('aria-expanded', isOpen);
});
mask.addEventListener('click', () => {
nav.classList.remove('open');
btn.setAttribute('aria-expanded', false);
});
横に並ぶか縦に並ぶかは、grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); の1行で、幅に合わせて自動で変わります。
ボタンからまるく広がる
押したボタンを中心に、メニューがまるく広がります。
押したところから広がるので、「いま押したものが開いた」と分かりやすい見せ方です。
コードを見る(HTML・CSS・JavaScript)
HTML
<div id="navArea">
<nav>
<ul>
<li><a href="#"><span>01</span>デザインのメモ</a></li>
<li><a href="#"><span>02</span>works</a></li>
<li><a href="#"><span>03</span>service</a></li>
<li><a href="#"><span>04</span>contact</a></li>
</ul>
</nav>
<button class="toggle_btn" aria-label="メニュー" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
CSS
/* ===== ボタンからまるく広がるタイプ ===== */
/* メニュー(ボタンの中心から、まるく広がる) */
#navArea nav {
position: fixed;
inset: 0;
z-index: 3;
display: grid;
place-items: center;
padding: 96px 32px 32px;
background: #e8dc6a;
clip-path: circle(0 at calc(100% - 40px) 40px);
visibility: hidden;
transition: .5s;
}
#navArea.open nav {
clip-path: circle(150vmax at calc(100% - 40px) 40px);
visibility: visible;
}
#navArea nav ul { width: min(360px, 100%); }
#navArea nav li { border-bottom: 1px solid rgba(43, 42, 39, .2); }
#navArea nav a { display: flex; align-items: baseline; gap: 16px; padding: 16px 0; font-size: 20px; }
#navArea nav a span { font-size: 12px; }
/* ボタン */
.toggle_btn {
position: fixed;
top: 16px;
right: 16px;
z-index: 4;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
background: #2b2a27;
cursor: pointer;
}
.toggle_btn span {
position: absolute;
left: 14px;
width: 20px;
height: 2px;
border-radius: 2px;
background: #fff;
transition: .5s;
}
.toggle_btn span:nth-child(1) { top: 17px; }
.toggle_btn span:nth-child(2) { top: 23px; }
.toggle_btn span:nth-child(3) { top: 29px; }
/* 開いたら × に */
#navArea.open .toggle_btn span:nth-child(1) { transform: translateY(6px) rotate(-45deg); }
#navArea.open .toggle_btn span:nth-child(2) { opacity: 0; }
#navArea.open .toggle_btn span:nth-child(3) { transform: translateY(-6px) rotate(45deg); }
@media (max-width: 600px) {
#navArea nav a { font-size: 18px; }
}
@media (prefers-reduced-motion: reduce) {
#navArea * { transition: none !important; }
}
JavaScript
const nav = document.getElementById('navArea');
const btn = nav.querySelector('.toggle_btn');
btn.addEventListener('click', () => {
const isOpen = nav.classList.toggle('open');
btn.setAttribute('aria-expanded', isOpen);
});
広げているのは clip-path: circle() です。
閉じているときは半径 0、開いたら画面より大きな半径にして、そのあいだを transition でつないでいます。at calc(100% - 40px) 40px がまるの中心で、ボタンのまん中に合わせています。ボタンの位置を変えたときは、ここも一緒に変えてください。
下から出す(ボトムシート)
ボタンを右下に置いて、メニューを下から出します。
スマホを片手で持つと、画面の上のほうは親指がとどきにくいですよね。ボタンもメニューも下にあると、片手のまま選べます。
コードを見る(HTML・CSS・JavaScript)
HTML
<div id="navArea">
<nav>
<ul>
<li><a href="#"><span>01</span>デザインのメモ</a></li>
<li><a href="#"><span>02</span>works</a></li>
<li><a href="#"><span>03</span>service</a></li>
<li><a href="#"><span>04</span>contact</a></li>
</ul>
</nav>
<button class="toggle_btn" aria-label="メニュー" aria-expanded="false">
<span></span><span></span><span></span>
</button>
<div id="mask"></div>
</div>
CSS
/* ===== 下から出すタイプ(ボトムシート) ===== */
/* メニュー(下から出る) */
#navArea nav {
position: fixed;
left: 0;
bottom: 0;
z-index: 3;
width: 100%;
max-height: 80vh;
overflow-y: auto;
padding: 28px 24px 88px; /* 下はボタンのぶんあける */
background: #fff;
border-radius: 16px 16px 0 0;
transform: translateY(100%);
visibility: hidden;
transition: .5s;
}
#navArea.open nav { transform: translateY(0); visibility: visible; }
/* 上のつまみ(引っぱれそうな見た目) */
#navArea nav::before {
content: "";
position: absolute;
top: 10px;
left: 50%;
width: 40px;
height: 4px;
margin-left: -20px;
border-radius: 2px;
background: #e8e3d8;
}
#navArea nav ul { max-width: 560px; margin: 0 auto; }
#navArea nav li { border-bottom: 1px solid #e8e3d8; }
#navArea nav a { display: flex; align-items: baseline; gap: 16px; padding: 16px 0; }
#navArea nav a span { font-size: 12px; color: #6b675f; }
/* うしろを暗くする */
#mask {
position: fixed;
inset: 0;
z-index: 2;
background: rgba(43, 42, 39, .5);
opacity: 0;
visibility: hidden;
transition: .5s;
}
#navArea.open #mask { opacity: 1; visibility: visible; }
/* ボタン */
.toggle_btn {
position: fixed;
right: 16px;
bottom: 16px; /* 親指がとどく、右下に */
z-index: 4;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
background: #2b2a27;
cursor: pointer;
}
.toggle_btn span {
position: absolute;
left: 14px;
width: 20px;
height: 2px;
border-radius: 2px;
background: #fff;
transition: .5s;
}
.toggle_btn span:nth-child(1) { top: 17px; }
.toggle_btn span:nth-child(2) { top: 23px; }
.toggle_btn span:nth-child(3) { top: 29px; }
/* 開いたら × に */
#navArea.open .toggle_btn span:nth-child(1) { transform: translateY(6px) rotate(-45deg); }
#navArea.open .toggle_btn span:nth-child(2) { opacity: 0; }
#navArea.open .toggle_btn span:nth-child(3) { transform: translateY(-6px) rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
#navArea * { transition: none !important; }
}
JavaScript
const nav = document.getElementById('navArea');
const btn = nav.querySelector('.toggle_btn');
const mask = document.getElementById('mask');
btn.addEventListener('click', () => {
const isOpen = nav.classList.toggle('open');
btn.setAttribute('aria-expanded', isOpen);
});
mask.addEventListener('click', () => {
nav.classList.remove('open');
btn.setAttribute('aria-expanded', false);
});
上の小さなつまみ(nav::before)は飾りですが、「下から出てきたもの」だと伝わりやすくなります。
iPhone のホームバーと重なるときは、下の余白に env(safe-area-inset-bottom) を足してください(viewport に viewport-fit=cover が必要です)。
スマホで使いやすくする5つのこと
見せ方を決めたら、次は使いやすさです。
ここからの5つは、どの見せ方にもそのまま足せます。下の見本は、5つを全部入れたものです。
コードを見る(HTML・CSS・JavaScript)
HTML
<div id="navArea">
<nav>
<ul>
<li><a href="#"><span>01</span>デザインのメモ</a></li>
<li><a href="#"><span>02</span>works</a></li>
<li><a href="#"><span>03</span>service</a></li>
<li><a href="#"><span>04</span>contact</a></li>
</ul>
</nav>
<button class="toggle_btn" aria-label="メニュー" aria-expanded="false">
<span></span><span></span><span></span>
</button>
<div id="mask"></div>
</div>
CSS
/* ===== スマホで使いやすくしたタイプ ===== */
/* メニュー(右から出る) */
#navArea nav {
position: fixed;
top: 0;
right: 0;
z-index: 3;
width: 300px;
height: 100%;
overflow-y: auto;
overscroll-behavior: contain; /* メニューの中のスクロールを、うしろに伝えない */
padding: 96px 32px 32px;
background: #e8dc6a;
transform: translateX(100%);
visibility: hidden; /* 閉じているあいだは、Tab キーでも入れない */
transition: transform .5s, visibility 0s .5s; /* 隠すのは、閉じ終わってから */
}
#navArea.open nav {
transform: translateX(0);
visibility: visible;
transition: transform .5s; /* 開くときは、すぐ見えるように(フォーカスを移せるように) */
}
#navArea nav li { border-bottom: 1px solid rgba(43, 42, 39, .2); }
/* 1行の高さは 48px より大きく */
#navArea nav a { display: flex; align-items: baseline; gap: 16px; padding: 16px 0; }
#navArea nav a span { font-size: 12px; }
/* キーボードで選んだところが見えるように */
#navArea a:focus-visible,
#navArea .toggle_btn:focus-visible { outline: 2px solid #2b2a27; outline-offset: 4px; }
/* 開いているあいだ、うしろのページはスクロールしない */
body.is-locked { overflow: hidden; }
/* うしろを暗くする */
#mask {
position: fixed;
inset: 0;
z-index: 2;
background: rgba(43, 42, 39, .5);
opacity: 0;
visibility: hidden;
transition: .5s;
}
#navArea.open #mask { opacity: 1; visibility: visible; }
/* ボタン */
.toggle_btn {
position: fixed;
top: 16px;
right: 16px;
z-index: 4;
width: 48px;
height: 48px;
border: 0;
border-radius: 50%;
background: #2b2a27;
cursor: pointer;
}
.toggle_btn span {
position: absolute;
left: 14px;
width: 20px;
height: 2px;
border-radius: 2px;
background: #fff;
transition: .5s;
}
.toggle_btn span:nth-child(1) { top: 17px; }
.toggle_btn span:nth-child(2) { top: 23px; }
.toggle_btn span:nth-child(3) { top: 29px; }
/* 開いたら × に */
#navArea.open .toggle_btn span:nth-child(1) { transform: translateY(6px) rotate(-45deg); }
#navArea.open .toggle_btn span:nth-child(2) { opacity: 0; }
#navArea.open .toggle_btn span:nth-child(3) { transform: translateY(-6px) rotate(45deg); }
@media (max-width: 600px) {
#navArea nav { width: 240px; padding: 88px 24px 24px; }
}
@media (prefers-reduced-motion: reduce) {
#navArea * { transition: none !important; }
}
JavaScript
const nav = document.getElementById('navArea');
const btn = nav.querySelector('.toggle_btn');
const mask = document.getElementById('mask');
const links = nav.querySelectorAll('nav a');
function openMenu() {
nav.classList.add('open');
btn.setAttribute('aria-expanded', true);
document.body.classList.add('is-locked'); // うしろのスクロールを止める
links[0].focus(); // メニューの1つ目へ
}
function closeMenu() {
nav.classList.remove('open');
btn.setAttribute('aria-expanded', false);
document.body.classList.remove('is-locked');
btn.focus(); // ボタンへ戻す
}
btn.addEventListener('click', () => {
nav.classList.contains('open') ? closeMenu() : openMenu();
});
mask.addEventListener('click', closeMenu); // うしろを押したら閉じる
links.forEach(a => a.addEventListener('click', closeMenu)); // リンクを押したら閉じる
document.addEventListener('keydown', e => { // Esc キーで閉じる
if (e.key === 'Escape' && nav.classList.contains('open')) closeMenu();
});
ボタンと項目は、48px より大きく
指で押すものは、縦も横も 48px くらいあると押しまちがえにくくなります。
見本のボタンは 48×48px、メニューの1行は上下の余白 16px で、48px より高くしています。三本線そのものは小さくても、押せる範囲を大きくしておけば大丈夫です。
開いているあいだ、うしろをスクロールさせない
メニューの上で指を動かしたら、うしろのページが動いてしまった、ということがよくあります。
開いたら body にクラスを付けて、うしろのスクロールを止めます。
body.is-locked { overflow: hidden; }
メニューの中が長くてスクロールするときは、overscroll-behavior: contain; も足すと、メニューの端まで来てもうしろに伝わりません。
閉じかたを、いくつも用意する
× のボタンだけだと、閉じかたに迷う人がいます。
うしろの暗いところを押しても、Esc キーでも閉じるようにしておくと安心です。
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && nav.classList.contains('open')) closeMenu();
});
リンクを押したら、閉じる
同じページの中へ移動するリンク(#about など)のときは、ページが変わらないので、メニューが開いたまま残ってしまいます。
リンクを押したら閉じるようにしておきます。
links.forEach(a => a.addEventListener('click', closeMenu));
キーボードでも使えるように
閉じているメニューは visibility: hidden; で隠しておくと、Tab キーで見えないリンクに入ってしまうことがありません。
開いたらメニューの1つ目へ、閉じたらボタンへフォーカスを戻すと、キーボードや読み上げで使う人も迷いません。
ひとつだけ注意があります。visibility も transition に入れていると、開いた瞬間はまだ隠れたままなので、フォーカスを移せません。
見本では、開くときは visibility をすぐ切りかえ、閉じるときだけ 0.5 秒待ってから隠しています。
#navArea nav { transition: transform .5s, visibility 0s .5s; }
#navArea.open nav { transition: transform .5s; }
見せ方の選び方
迷ったときは、こんなふうに選んでみてください。
- 項目が少なくて、雰囲気を大事にしたい → 全画面かまるく広がる
- 項目が多くて、ページを見ながら選んでほしい → 上からか横から(前の記事)
- スマホで見る人が多い → 下から
どれを選んでも、「スマホで使いやすくする5つのこと」は入れておくのがおすすめです。
さいごに
ハンバーガーメニューは、押したあとの見せ方で、サイトの印象がかなり変わります。
見本のコードは色と動きの時間だけ変えれば、そのまま使えます。気になったものから試してみてください。
ボタンの動き(× になるときのエフェクト19種)は、前の記事にまとめています。





