はじめに
Web制作を始めて間もない方なら、誰もが一度は悩むハンバーガーメニューの実装。
HTMLとCSSの基礎は理解できても、スムーズなアニメーションやレスポンシブ対応となると、思うように実装できないものですよね。
特に、ポートフォリオサイトではモダンで印象的なUIを実現したくても、JavaScriptを使った実装方法や、アクセシビリティに配慮したコーディングとなると、なかなか難しいものです。
この記事では、現場で使える5種類のハンバーガーメニューを、コピー&ペーストで実装できるようにご紹介します。各実装例は、以下の要素を重視して解説していきます:
- モダンなアニメーション効果
- レスポンシブ対応
- アクセシビリティへの配慮
- 実装時の注意点
- トラブルシューティング
パターン1:基本的なスライドインメニュー
まずは、最もポピュラーな左からスライドインするハンバーガーメニューの実装方法をご紹介します。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
<div class="header">
<button class="hamburger" aria-label="メニュー" aria-controls="nav-menu" aria-expanded="false">
<span class="hamburger__line"></span>
<span class="hamburger__line"></span>
<span class="hamburger__line"></span>
</button>
<nav id="nav-menu" class="nav" aria-hidden="true">
<ul class="nav__list">
<li class="nav__item"><a href="#" class="nav__link">ホーム</a></li>
<li class="nav__item"><a href="#" class="nav__link">about</a></li>
<li class="nav__item"><a href="#" class="nav__link">サービス</a></li>
<li class="nav__item"><a href="#" class="nav__link">お問い合わせ</a></li>
</ul>
</nav>
</div>
.header {
position: relative;
padding: 20px;
}
.hamburger {
position: fixed;
top: 20px;
right: 20px;
z-index: 100;
width: 48px;
height: 48px;
border: none;
background: transparent;
cursor: pointer;
}
.hamburger__line {
position: absolute;
left: 11px;
width: 26px;
height: 2px;
background-color: #333;
transition: all .4s;
}
.hamburger__line:nth-of-type(1) {
top: 14px;
}
.hamburger__line:nth-of-type(2) {
top: 23px;
}
.hamburger__line:nth-of-type(3) {
top: 32px;
}
/* メニューオープン時 */
.hamburger.active .hamburger__line:nth-of-type(1) {
transform: translateY(9px) rotate(-45deg);
}
.hamburger.active .hamburger__line:nth-of-type(2) {
opacity: 0;
}
.hamburger.active .hamburger__line:nth-of-type(3) {
transform: translateY(-9px) rotate(45deg);
}
.nav {
position: fixed;
top: 0;
left: 0;
width: 300px;
height: 100vh;
background-color: #fff;
box-shadow: 2px 0 4px rgba(0,0,0,.1);
transform: translateX(-100%);
transition: transform .4s;
z-index: 90;
}
.nav.active {
transform: translateX(0);
}
.nav__list {
margin: 0;
padding: 100px 0 0;
list-style: none;
}
.nav__item {
padding: 0 20px;
}
.nav__link {
display: block;
padding: 15px 0;
color: #333;
text-decoration: none;
border-bottom: 1px solid #eee;
}
// script.js
document.addEventListener('DOMContentLoaded', () => {
const hamburger = document.querySelector('.hamburger');
const nav = document.querySelector('.nav');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
nav.classList.toggle('active');
// アクセシビリティ対応
const isOpen = hamburger.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
nav.setAttribute('aria-hidden', !isOpen);
});
// メニューの外側をクリックした時の処理
document.addEventListener('click', (e) => {
if (!e.target.closest('.nav') && !e.target.closest('.hamburger') && nav.classList.contains('active')) {
hamburger.classList.remove('active');
nav.classList.remove('active');
hamburger.setAttribute('aria-expanded', false);
nav.setAttribute('aria-hidden', true);
}
});
});
実装のポイント解説:
- アクセシビリティへの配慮
- aria-label属性でボタンの役割を明示
- aria-expanded属性でメニューの開閉状態を通知
- aria-hidden属性でスクリーンリーダーへの表示制御
- スムーズなアニメーション
- transformとtransitionを使用した滑らかな動き
- ハンバーガーアイコンの×への変形アニメーション
- レスポンシブ対応
- vw/vh単位を使用した画面サイズ対応
- メディアクエリは不要な最小限の実装
- UX向上のための実装
- メニュー外クリックでの閉じる機能
- タッチデバイスでの操作性考慮
パターン2:オーバーレイ&円形展開メニュー
2つ目は、画面全体をカバーするオーバーレイタイプと、ボタンを起点に円形に展開するスタイリッシュなメニューをご紹介します。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
<div class="header">
<button class="hamburger-overlay" aria-label="メニュー" aria-controls="overlay-menu" aria-expanded="false">
<span class="hamburger-overlay__line"></span>
<span class="hamburger-overlay__line"></span>
<span class="hamburger-overlay__line"></span>
</button>
<nav id="overlay-menu" class="nav-overlay" aria-hidden="true">
<div class="nav-overlay__content">
<ul class="nav-overlay__list">
<li class="nav-overlay__item"><a href="#" class="nav-overlay__link">ホーム</a></li>
<li class="nav-overlay__item"><a href="#" class="nav-overlay__link">サービス</a></li>
<li class="nav-overlay__item"><a href="#" class="nav-overlay__link">works</a></li>
<li class="nav-overlay__item"><a href="#" class="nav-overlay__link">お問い合わせ</a></li>
</ul>
</div>
</nav>
</div>
/* overlay-styles.css */
.hamburger-overlay {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 48px;
height: 48px;
border: none;
background: transparent;
cursor: pointer;
}
.hamburger-overlay__line {
position: absolute;
left: 11px;
width: 26px;
height: 2px;
background-color: #333;
transition: all .6s;
}
.hamburger-overlay__line:nth-of-type(1) { top: 14px; }
.hamburger-overlay__line:nth-of-type(2) { top: 23px; }
.hamburger-overlay__line:nth-of-type(3) { top: 32px; }
.hamburger-overlay.active .hamburger-overlay__line {
background-color: #fff;
}
.hamburger-overlay.active .hamburger-overlay__line:nth-of-type(1) {
transform: translateY(9px) rotate(-45deg);
}
.hamburger-overlay.active .hamburger-overlay__line:nth-of-type(2) {
opacity: 0;
}
.hamburger-overlay.active .hamburger-overlay__line:nth-of-type(3) {
transform: translateY(-9px) rotate(45deg);
}
.nav-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.95);
visibility: hidden;
opacity: 0;
transition: all .6s;
z-index: 900;
}
.nav-overlay.active {
visibility: visible;
opacity: 1;
}
.nav-overlay__content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
text-align: center;
}
.nav-overlay__list {
margin: 0;
padding: 0;
list-style: none;
}
.nav-overlay__item {
opacity: 0;
transform: translateY(20px);
transition: all .6s;
}
.nav-overlay.active .nav-overlay__item {
opacity: 1;
transform: translateY(0);
}
.nav-overlay.active .nav-overlay__item:nth-child(1) { transition-delay: 0.1s; }
.nav-overlay.active .nav-overlay__item:nth-child(2) { transition-delay: 0.2s; }
.nav-overlay.active .nav-overlay__item:nth-child(3) { transition-delay: 0.3s; }
.nav-overlay.active .nav-overlay__item:nth-child(4) { transition-delay: 0.4s; }
.nav-overlay__link {
display: inline-block;
padding: 20px;
color: #fff;
font-size: 24px;
text-decoration: none;
transition: color .3s;
}
.nav-overlay__link:hover {
color: #4a90e2;
}
// overlay-script.js
document.addEventListener('DOMContentLoaded', () => {
const hamburger = document.querySelector('.hamburger-overlay');
const nav = document.querySelector('.nav-overlay');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
nav.classList.toggle('active');
const isOpen = hamburger.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
nav.setAttribute('aria-hidden', !isOpen);
// メニューオープン時に背景スクロールを防止
document.body.style.overflow = isOpen ? 'hidden' : '';
});
// ESCキーでメニューを閉じる
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && nav.classList.contains('active')) {
hamburger.classList.remove('active');
nav.classList.remove('active');
hamburger.setAttribute('aria-expanded', false);
nav.setAttribute('aria-hidden', true);
document.body.style.overflow = '';
}
});
});
実装のポイント:
- スムーズなオーバーレイ表示
- visibility と opacity の組み合わせによるスムーズな表示/非表示
- transition-delay を使用したメニュー項目の段階的な表示
- アクセシビリティ強化
- ESCキーでのメニュー閉じる機能
- スクロール制御による背景固定
- キーボード操作のサポート
- 視覚的な工夫
- メニュー項目の段階的なフェードイン
- ホバー時のインタラクション
- 背景の透過による奥行き表現
パターン3:モーフィングアニメーションメニュー
3つ目は、ハンバーガーアイコンがスムーズに変形するモーフィングアニメーションを実装したメニューです。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
<div class="header">
<button class="hamburger-morph" aria-label="メニュー" aria-controls="morph-menu" aria-expanded="false">
<svg class="hamburger-morph__icon" width="48" height="48" viewBox="0 0 100 100">
<path class="hamburger-morph__line" d="M 20,29 H 80 C 80,29 94.5,28.817352 94.532987,66.711331 94.543142,77.980673 90.966081,81.670246 85.259173,81.668997 79.552261,81.667751 75.000211,74.999942 75.000211,74.999942 L 25.000021,25.000058" />
<path class="hamburger-morph__line" d="M 20,50 H 80" />
<path class="hamburger-morph__line" d="M 20,71 H 80 C 80,71 94.5,71.182648 94.532987,33.288669 94.543142,22.019327 90.966081,18.329754 85.259173,18.331003 79.552261,18.332249 75.000211,25.000058 75.000211,25.000058 L 25.000021,74.999942" />
</svg>
</button>
<nav id="morph-menu" class="nav-morph" aria-hidden="true">
<div class="nav-morph__wrapper">
<ul class="nav-morph__list">
<li class="nav-morph__item">
<a href="#" class="nav-morph__link">
<span class="nav-morph__text">Home</span>
<span class="nav-morph__hover">ホーム</span>
</a>
</li>
<li class="nav-morph__item">
<a href="#" class="nav-morph__link">
<span class="nav-morph__text">About</span>
<span class="nav-morph__hover">私たちについて</span>
</a>
</li>
<li class="nav-morph__item">
<a href="#" class="nav-morph__link">
<span class="nav-morph__text">Works</span>
<span class="nav-morph__hover">制作実績</span>
</a>
</li>
<li class="nav-morph__item">
<a href="#" class="nav-morph__link">
<span class="nav-morph__text">Contact</span>
<span class="nav-morph__hover">お問い合わせ</span>
</a>
</li>
</ul>
</div>
</nav>
</div>
.hamburger-morph {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 48px;
height: 48px;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
}
.hamburger-morph__icon {
width: 100%;
height: 100%;
}
.hamburger-morph__line {
fill: none;
stroke: #000;
stroke-width: 6;
transition: stroke-dasharray 600ms cubic-bezier(0.4, 0, 0.2, 1),
stroke-dashoffset 600ms cubic-bezier(0.4, 0, 0.2, 1);
}
.hamburger-morph__line:nth-child(1) {
stroke-dasharray: 60 207;
}
.hamburger-morph__line:nth-child(2) {
stroke-dasharray: 60 60;
}
.hamburger-morph__line:nth-child(3) {
stroke-dasharray: 60 207;
}
.hamburger-morph.active .hamburger-morph__line:nth-child(1) {
stroke-dasharray: 90 207;
stroke-dashoffset: -134;
}
.hamburger-morph.active .hamburger-morph__line:nth-child(2) {
stroke-dasharray: 1 60;
stroke-dashoffset: -30;
}
.hamburger-morph.active .hamburger-morph__line:nth-child(3) {
stroke-dasharray: 90 207;
stroke-dashoffset: -134;
}
.nav-morph {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: rgba(29, 29, 31, 0.98);
clip-path: circle(0% at calc(100% - 44px) 44px);
transition: clip-path 0.7s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 900;
}
.nav-morph.active {
clip-path: circle(150% at calc(100% - 44px) 44px);
}
.nav-morph__wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.nav-morph__list {
margin: 0;
padding: 0;
list-style: none;
text-align: center;
}
.nav-morph__item {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.4s ease, transform 0.4s ease;
}
.nav-morph.active .nav-morph__item {
opacity: 1;
transform: translateY(0);
}
.nav-morph.active .nav-morph__item:nth-child(1) { transition-delay: 0.3s; }
.nav-morph.active .nav-morph__item:nth-child(2) { transition-delay: 0.4s; }
.nav-morph.active .nav-morph__item:nth-child(3) { transition-delay: 0.5s; }
.nav-morph.active .nav-morph__item:nth-child(4) { transition-delay: 0.6s; }
.nav-morph__link {
position: relative;
display: inline-block;
padding: 20px;
font-size: 28px;
color: #fff;
text-decoration: none;
overflow: hidden;
}
.nav-morph__text,
.nav-morph__hover {
display: block;
transition: transform 0.3s ease;
}
.nav-morph__hover {
position: absolute;
top: 100%;
left: 0;
width: 100%;
transform: translateY(0%);
}
.nav-morph__link:hover .nav-morph__text {
transform: translateY(-100%);
}
.nav-morph__link:hover .nav-morph__hover {
transform: translateY(-100%);
}
document.addEventListener('DOMContentLoaded', () => {
const hamburger = document.querySelector('.hamburger-morph');
const nav = document.querySelector('.nav-morph');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
nav.classList.toggle('active');
const isOpen = hamburger.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
nav.setAttribute('aria-hidden', !isOpen);
document.body.style.overflow = isOpen ? 'hidden' : '';
});
// メニューリンクのホバーエフェクト用のイベントリスナー
const menuLinks = document.querySelectorAll('.nav-morph__link');
menuLinks.forEach(link => {
link.addEventListener('mouseenter', () => {
link.querySelector('.nav-morph__text').style.transform = 'translateY(-100%)';
link.querySelector('.nav-morph__hover').style.transform = 'translateY(-100%)';
});
link.addEventListener('mouseleave', () => {
link.querySelector('.nav-morph__text').style.transform = 'translateY(0)';
link.querySelector('.nav-morph__hover').style.transform = 'translateY(0)';
});
});
});
実装のポイント:
- SVGを使用したモーフィングアニメーション
- stroke-dasharray と stroke-dashoffset を使用した滑らかな変形
- cubic-bezier による緩急のある動き
- クリエイティブなメニュー展開
- clip-path を使用した円形展開アニメーション
- 順次表示されるメニュー項目
- インタラクティブな要素
- 二重テキストによるホバーエフェクト
- スムーズなテキストスライド
これらのコードは、モダンなWebサイトやポートフォリオサイトにそのまま実装できます。
パターン4:スライド&フェードメニュー
4つ目は、スライドとフェードを組み合わせた高級感のあるアニメーションメニューです。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
<div class="header">
<button class="hamburger-fade" aria-label="メニュー" aria-controls="fade-menu" aria-expanded="false">
<div class="hamburger-fade__wrapper">
<span class="hamburger-fade__line"></span>
<span class="hamburger-fade__line"></span>
<span class="hamburger-fade__line"></span>
</div>
</button>
<nav id="fade-menu" class="nav-fade" aria-hidden="true">
<div class="nav-fade__bg"></div>
<div class="nav-fade__wrapper">
<ul class="nav-fade__list">
<li class="nav-fade__item">
<span class="nav-fade__number">01</span>
<a href="#" class="nav-fade__link">Home</a>
</li>
<li class="nav-fade__item">
<span class="nav-fade__number">02</span>
<a href="#" class="nav-fade__link">About</a>
</li>
<li class="nav-fade__item">
<span class="nav-fade__number">03</span>
<a href="#" class="nav-fade__link">Works</a>
</li>
<li class="nav-fade__item">
<span class="nav-fade__number">04</span>
<a href="#" class="nav-fade__link">Contact</a>
</li>
</ul>
<div class="nav-fade__info">
<p class="nav-fade__address">東京都渋谷区○○ 1-2-3</p>
<p class="nav-fade__tel">03-1234-5678</p>
</div>
</div>
</nav>
</div>
/* fade-styles.css */
.hamburger-fade {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 60px;
height: 60px;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
}
.hamburger-fade__wrapper {
position: relative;
width: 30px;
height: 20px;
margin: 20px auto;
}
.hamburger-fade__line {
position: absolute;
left: 0;
width: 100%;
height: 2px;
background-color: #333;
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
}
.hamburger-fade__line:nth-child(1) { top: 0; }
.hamburger-fade__line:nth-child(2) { top: 9px; }
.hamburger-fade__line:nth-child(3) { top: 18px; }
.hamburger-fade.active .hamburger-fade__line {
background-color: #fff;
}
.hamburger-fade.active .hamburger-fade__line:nth-child(1) {
transform: translateY(9px) rotate(45deg);
}
.hamburger-fade.active .hamburger-fade__line:nth-child(2) {
opacity: 0;
transform: translateX(20px);
}
.hamburger-fade.active .hamburger-fade__line:nth-child(3) {
transform: translateY(-9px) rotate(-45deg);
}
.nav-fade {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
visibility: hidden;
z-index: 900;
}
.nav-fade__bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.95);
opacity: 0;
transition: opacity 0.5s cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-fade.active {
visibility: visible;
}
.nav-fade.active .nav-fade__bg {
opacity: 1;
}
.nav-fade__wrapper {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 5vh 10vw;
}
.nav-fade__list {
margin: 0;
padding: 0;
list-style: none;
}
.nav-fade__item {
position: relative;
margin-bottom: 2vh;
padding-left: 60px;
opacity: 0;
transform: translateY(20px);
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-fade.active .nav-fade__item {
opacity: 1;
transform: translateY(0);
}
.nav-fade.active .nav-fade__item:nth-child(1) { transition-delay: 0.2s; }
.nav-fade.active .nav-fade__item:nth-child(2) { transition-delay: 0.3s; }
.nav-fade.active .nav-fade__item:nth-child(3) { transition-delay: 0.4s; }
.nav-fade.active .nav-fade__item:nth-child(4) { transition-delay: 0.5s; }
.nav-fade__number {
position: absolute;
left: 0;
color: #666;
font-size: 14px;
font-family: 'Roboto', sans-serif;
}
.nav-fade__link {
display: inline-block;
color: #fff;
font-size: 32px;
text-decoration: none;
transition: color 0.3s ease;
}
.nav-fade__link:hover {
color: #4a90e2;
}
.nav-fade__info {
margin-top: auto;
padding-left: 60px;
color: #666;
font-size: 14px;
opacity: 0;
transform: translateY(20px);
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0.6s;
}
.nav-fade.active .nav-fade__info {
opacity: 1;
transform: translateY(0);
}
.nav-fade__address,
.nav-fade__tel {
margin: 5px 0;
}
@media (max-width: 768px) {
.nav-fade__link {
font-size: 24px;
}
.nav-fade__item {
padding-left: 40px;
margin-bottom: 1.5vh;
}
.nav-fade__info {
padding-left: 40px;
}
}
// fade-script.js
document.addEventListener('DOMContentLoaded', () => {
const hamburger = document.querySelector('.hamburger-fade');
const nav = document.querySelector('.nav-fade');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
nav.classList.toggle('active');
const isOpen = hamburger.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
nav.setAttribute('aria-hidden', !isOpen);
document.body.style.overflow = isOpen ? 'hidden' : '';
});
// メニューリンクにホバーエフェクトを追加
const menuLinks = document.querySelectorAll('.nav-fade__link');
menuLinks.forEach(link => {
link.addEventListener('mouseover', () => {
const number = link.parentElement.querySelector('.nav-fade__number');
number.style.color = '#4a90e2';
});
link.addEventListener('mouseout', () => {
const number = link.parentElement.querySelector('.nav-fade__number');
number.style.color = '#666';
});
});
});
実装のポイント:
- 高級感のある演出
- 番号とテキストの組み合わせ
- 連続的なアニメーション
- 洗練された配色
- レスポンシブ対応
- フォントサイズの可変
- パディングの調整
- メディアクエリによる最適化
- インタラクティブ要素
- 番号とリンクの連動したホバーエフェクト
- スムーズなフェードイン
- 背景のオーバーレイ効果
パターン5:グリッドアニメーションメニュー
5つ目は、グリッドレイアウトを活用したモダンなメニューです。
メニュー展開時にグリッドが動的に変化する演出が特徴です。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
<div class="header">
<button class="hamburger-grid" aria-label="メニュー" aria-controls="grid-menu" aria-expanded="false">
<div class="hamburger-grid__dots">
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
<span class="hamburger-grid__dot"></span>
</div>
</button>
<nav id="grid-menu" class="nav-grid" aria-hidden="true">
<div class="nav-grid__content">
<div class="nav-grid__sections">
<section class="nav-grid__section">
<h2 class="nav-grid__title">Menu</h2>
<ul class="nav-grid__list">
<li><a href="#" class="nav-grid__link">Home</a></li>
<li><a href="#" class="nav-grid__link">About</a></li>
<li><a href="#" class="nav-grid__link">Works</a></li>
<li><a href="#" class="nav-grid__link">Contact</a></li>
</ul>
</section>
<section class="nav-grid__section">
<h2 class="nav-grid__title">Social</h2>
<ul class="nav-grid__list">
<li><a href="#" class="nav-grid__link">Twitter</a></li>
<li><a href="#" class="nav-grid__link">Instagram</a></li>
<li><a href="#" class="nav-grid__link">GitHub</a></li>
</ul>
</section>
</div>
</div>
</nav>
</div>
.hamburger-grid {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 50px;
height: 50px;
padding: 10px;
border: none;
background: transparent;
cursor: pointer;
}
.hamburger-grid__dots {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
width: 100%;
height: 100%;
}
.hamburger-grid__dot {
width: 100%;
height: 100%;
background-color: #333;
border-radius: 50%;
transition: transform 0.3s ease, background-color 0.3s ease;
}
.hamburger-grid.active .hamburger-grid__dot {
background-color: #fff;
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(1) {
transform: scale(0);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(2) {
transform: translateY(8px);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(3) {
transform: scale(0);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(4) {
transform: translateX(8px);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(5) {
transform: scale(1.2);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(6) {
transform: translateX(-8px);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(7) {
transform: scale(0);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(8) {
transform: translateY(-8px);
}
.hamburger-grid.active .hamburger-grid__dot:nth-child(9) {
transform: scale(0);
}
.nav-grid {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-color: #1a1a1a;
visibility: hidden;
opacity: 0;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 900;
}
.nav-grid.active {
visibility: visible;
opacity: 1;
}
.nav-grid__content {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
max-width: 1200px;
height: 100%;
margin: 0 auto;
padding: 100px 40px;
}
.nav-grid__sections {
grid-column: span 12;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 40px;
}
.nav-grid__section {
opacity: 0;
transform: translateY(20px);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-grid.active .nav-grid__section {
opacity: 1;
transform: translateY(0);
}
.nav-grid.active .nav-grid__section:nth-child(1) {
transition-delay: 0.2s;
}
.nav-grid.active .nav-grid__section:nth-child(2) {
transition-delay: 0.3s;
}
.nav-grid__title {
margin: 0 0 20px;
color: #666;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 2px;
}
.nav-grid__list {
margin: 0;
padding: 0;
list-style: none;
}
.nav-grid__list li {
margin-bottom: 15px;
overflow: hidden;
}
.nav-grid__link {
display: inline-block;
color: #fff;
font-size: 24px;
text-decoration: none;
transform: translateY(100%);
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
color 0.3s ease;
}
.nav-grid.active .nav-grid__link {
transform: translateY(0);
}
.nav-grid__link:hover {
color: #4a90e2;
}
@media (max-width: 768px) {
.nav-grid__content {
padding: 80px 20px;
}
.nav-grid__sections {
grid-template-columns: 1fr;
}
.nav-grid__link {
font-size: 20px;
}
}
document.addEventListener('DOMContentLoaded', () => {
const hamburger = document.querySelector('.hamburger-grid');
const nav = document.querySelector('.nav-grid');
const links = document.querySelectorAll('.nav-grid__link');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
nav.classList.toggle('active');
const isOpen = hamburger.classList.contains('active');
hamburger.setAttribute('aria-expanded', isOpen);
nav.setAttribute('aria-hidden', !isOpen);
document.body.style.overflow = isOpen ? 'hidden' : '';
// リンクのアニメーションディレイを設定
if (isOpen) {
links.forEach((link, index) => {
link.style.transitionDelay = `${0.2 + (index * 0.1)}s`;
});
} else {
links.forEach(link => {
link.style.transitionDelay = '0s';
});
}
});
// アクセシビリティのためのキーボードナビゲーション
nav.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && nav.classList.contains('active')) {
hamburger.click();
}
});
});
実装のポイント:
- グリッドレイアウトの活用
- CSS Gridによる柔軟なレイアウト
- レスポンシブ対応の簡略化
- セクション分けによる整理された構造
- ドットアニメーション
- 個別のドットの動きによる視覚的な楽しさ
- スケールと移動を組み合わせた演出
- 滑らかな色の変化
- 段階的なアニメーション
- セクションごとの表示タイミング制御
- リンクの順次表示
- スムーズな出現と消失
さいごに
ここまで5種類のモダンなハンバーガーメニューの実装方法をご紹介してきました。それぞれのデザインや動きの特徴を活かして、サイトの雰囲気に合わせて使い分けていただければと思います。
実装時の注意点
アクセシビリティに関しては、aria属性を適切に使用し、キーボード操作やスクリーンリーダーでの利用に配慮することが重要です。特にモバイルデバイスでの操作性を重視し、タッチデバイスでの挙動確認も忘れずに行いましょう。
パフォーマンス面では、アニメーションにtransformとopacityを優先的に使用することで、スムーズな動作を実現できます。また、不要なDOM操作を最小限に抑えることで、軽量な実装を維持することができます。
カスタマイズのポイント
今回ご紹介したコードは基本的な実装例ですが、実際のプロジェクトに導入する際は、サイトのデザインに合わせて配色やフォント、アニメーション速度などを調整してください。さらに、検索機能や言語切替など、必要に応じて機能を追加することで、よりユーザーフレンドリーなナビゲーションを実現できます。