【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種)は、前の記事にまとめています。

デザオ
まるく広がるの、押すのが楽しいですねー
デザコト
見た目を決めたら、使いやすさの5つも忘れずにですよー

ふたりの小言

デザコトauthor

あ、いいな、と思うWebデザインを紹介しています。デザインの参考に。やさしいデザインが多いです。Webデザインギャラリー『デザインのこと - Web design gallery』を運営しています。