はじめに
Web制作を独学で学び始めて、HTMLとCSSの基礎は理解できたものの、いざJavaScriptを使った実践的な実装となると、どこから手をつければいいのか迷ってしまうものです。
特に、多くのWebサイトで使用されているアコーディオンメニュー。
シンプルそうに見えて、実は適切な実装方法を知らないと、思わぬバグやスマートフォンでの表示崩れに悩まされることになります。
この記事では、モダンなアコーディオンメニューの実装方法を、5つのパターンで詳しく解説します。
全てのコードはコピー&ペーストですぐに使え、さらにカスタマイズのポイントも押さえているので、あなたのポートフォリオやクライアントワークですぐに活用できますので、ぜひ参考にしてみてください。
基本のアコーディオンは以下で解説しています。
1. シンプルなフェードインアコーディオン
まずは、最もベーシックでありながら、モダンな実装方法のアコーディオンメニューから見ていきましょう。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
この実装の特徴は以下の通りです。
- CSSアニメーションによるスムーズな動き
- JavaScriptでの適切なアクセシビリティ対応
- レスポンシブデザインへの対応
- 軽量で高パフォーマンス
HTML
<div class="accordion">
<button class="accordion-trigger" aria-expanded="false">
<span>アコーディオンの見出し</span>
<svg class="accordion-icon" width="24" height="24" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"></path>
</svg>
</button>
<div class="accordion-content">
<div class="accordion-body">
アコーディオンの内容がここに表示されます。
</div>
</div>
</div>
CSS
.accordion {
width: 100%;
max-width: 600px;
margin: 0 auto;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
overflow: hidden;
}
.accordion-trigger {
width: 100%;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
background: white;
border: none;
cursor: pointer;
transition: background-color 0.2s;
}
.accordion-trigger:hover {
background-color: #f8fafc;
}
.accordion-icon {
transition: transform 0.2s;
}
.accordion-trigger[aria-expanded="true"] .accordion-icon {
transform: rotate(180deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-in-out;
}
.accordion-content.open {
max-height: 200px;
}
.accordion-body {
padding: 1rem;
}
JavaScript
document.querySelectorAll('.accordion-trigger').forEach(button => {
button.addEventListener('click', () => {
const content = button.nextElementSibling;
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
content.classList.toggle('open');
});
});
コードの解説
- HTML構造
button
要素を使用することで、キーボード操作に対応aria-expanded
属性でアクセシビリティを確保
- CSSの特徴
max-height
を使用したスムーズなアニメーションtransition
プロパティによる自然な動き- レスポンシブ対応のための
max-width
設定
- JavaScriptのポイント
- 純粋なJavaScript(バニラJS)での実装
- WAI-ARIAに準拠したアクセシビリティ対応
- イベントデリゲーションによる効率的な処理
2.複数パネル対応のアコーディオングループ
よく見かけるFAQや質問回答形式で使われる、複数のパネルを制御する実用的なアコーディオンを実装です。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
HTML
<div class="faq-accordion">
<div class="panel">
<button class="panel-header">
Q1. アコーディオンメニューとは何ですか?
<span class="icon">+</span>
</button>
<div class="panel-content">
<div class="panel-body">
<p>
アコーディオンメニューは、クリックすると内容が展開・収納される
UIコンポーネントです。限られたスペースで多くの情報を
整理して表示できる便利な機能です。
</p>
</div>
</div>
</div>
<div class="panel">
<button class="panel-header">
Q2. メリットを教えてください
<span class="icon">+</span>
</button>
<div class="panel-content">
<div class="panel-body">
<p>
主なメリットは以下の3つです:<br>
・画面スペースを効率的に使える<br>
・情報を整理して表示できる<br>
・ユーザーが必要な情報を選んで閲覧できる
</p>
</div>
</div>
</div>
</div>
CSS
.faq-accordion {
max-width: 800px;
margin: 0 auto;
}
.panel {
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 10px;
}
.panel-header {
width: 100%;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
border: none;
cursor: pointer;
font-size: 16px;
text-align: left;
}
.panel-header:hover {
background: #f8f9fa;
}
.icon {
font-size: 20px;
transition: transform 0.3s;
}
.panel-header.active .icon {
transform: rotate(45deg);
}
.panel-content {
height: 0;
overflow: hidden;
transition: height 0.3s ease-out;
}
.panel-body {
padding: 20px;
border-top: 1px solid #eee;
}
JavaScript
document.querySelectorAll('.panel-header').forEach(trigger => {
trigger.addEventListener('click', function() {
const content = this.nextElementSibling;
const panel = this.closest('.panel');
// 他のパネルを閉じる
document.querySelectorAll('.panel-header').forEach(otherTrigger => {
if (otherTrigger !== this) {
otherTrigger.classList.remove('active');
const otherContent = otherTrigger.nextElementSibling;
otherContent.style.height = '0';
}
});
// クリックしたパネルの処理
this.classList.toggle('active');
if (this.classList.contains('active')) {
// 開く時は中身の高さを計算
const body = content.querySelector('.panel-body');
content.style.height = body.offsetHeight + 'px';
} else {
// 閉じる時は0
content.style.height = '0';
}
});
});
実装のポイント
- 構造のポイント
- 内容を包む要素を2重にして、パディングの変化を防ぐ
- アイコンは疑似要素ではなくspanで実装し、回転を管理しやすく
- ボタン要素を使用してキーボード操作に対応
- CSSのポイント
- heightプロパティでアニメーション(max-heightより自然な動き)
- パディングは固定値で持ち、高さだけを変化
- 余計なアニメーションを入れすぎない
- JavaScriptのポイント
- 実際の高さを計算して設定
- パネルごとに適切な高さでアニメーション
- 他のパネルを確実に閉じる制御
3.モダンなアニメーション付きアコーディオン
より洗練された見た目と動きを実現する、アニメーション重視のアコーディオンメニューを実装です。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
HTML
<div class="modern-accordion">
<div class="accordion-item">
<button class="accordion-header">
<div class="header-content">
<span class="label">Section 1</span>
<p class="title">基本的な使い方</p>
</div>
<div class="icon-wrap">
<span class="line"></span>
<span class="line"></span>
</div>
</button>
<div class="accordion-body">
<div class="body-content">
<p>基本的な使い方について説明します。まずは基礎から始めて、徐々に応用的な使い方を学んでいきましょう。</p>
<ul>
<li>手順1: インストール方法</li>
<li>手順2: 初期設定</li>
<li>手順3: 基本操作</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header">
<div class="header-content">
<span class="label">Section 2</span>
<p class="title">カスタマイズ</p>
</div>
<div class="icon-wrap">
<span class="line"></span>
<span class="line"></span>
</div>
</button>
<div class="accordion-body">
<div class="body-content">
<p>見た目や動きをカスタマイズする方法を解説します。CSSとJavaScriptを使って様々なアレンジが可能です。</p>
</div>
</div>
</div>
</div>
CSS
.modern-accordion {
max-width: 800px;
margin: 0 auto;
--primary-color: #2563eb;
--text-color: #1f2937;
--border-color: #e5e7eb;
}
.accordion-item {
border: 1px solid var(--border-color);
border-radius: 12px;
margin-bottom: 16px;
overflow: hidden;
background: white;
}
.accordion-header {
width: 100%;
padding: 24px;
display: flex;
justify-content: space-between;
align-items: center;
background: none;
border: none;
cursor: pointer;
transition: background-color 0.2s;
}
.accordion-header:hover {
background-color: #f8fafc;
}
.header-content {
text-align: left;
}
.label {
font-size: 14px;
color: var(--primary-color);
margin-bottom: 4px;
display: block;
}
.title {
font-size: 18px;
color: var(--text-color);
margin: 0;
font-weight: 500;
}
.icon-wrap {
position: relative;
width: 24px;
height: 24px;
}
.line {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 2px;
background-color: var(--text-color);
transition: transform 0.3s ease;
}
.line:first-child {
transform: translateY(-50%);
}
.line:last-child {
transform: translateY(-50%) rotate(90deg);
}
.accordion-header.active .line:last-child {
transform: translateY(-50%) rotate(0);
}
.accordion-body {
height: 0;
overflow: hidden;
transition: height 0.3s ease-out;
}
.body-content {
padding: 0 24px 24px;
}
/* アニメーション用の状態 */
.accordion-body.entering {
height: var(--content-height);
}
.accordion-body.leaving {
height: 0;
}
JavaScript
document.querySelectorAll('.accordion-header').forEach(header => {
header.addEventListener('click', function() {
const body = this.nextElementSibling;
const content = body.querySelector('.body-content');
const isExpanding = !this.classList.contains('active');
// 他のアコーディオンを閉じる
document.querySelectorAll('.accordion-header').forEach(otherHeader => {
if (otherHeader !== this && otherHeader.classList.contains('active')) {
otherHeader.classList.remove('active');
const otherBody = otherHeader.nextElementSibling;
closeAccordion(otherBody);
}
});
// クリックされたアコーディオンの処理
this.classList.toggle('active');
if (isExpanding) {
// 開く処理
const height = content.offsetHeight;
body.style.height = `${height}px`;
} else {
// 閉じる処理
closeAccordion(body);
}
});
});
function closeAccordion(element) {
// 高さを0にして閉じる
element.style.height = '0';
}
// ウィンドウリサイズ時の高さ調整
window.addEventListener('resize', () => {
document.querySelectorAll('.accordion-header.active').forEach(header => {
const body = header.nextElementSibling;
const content = body.querySelector('.body-content');
body.style.height = `${content.offsetHeight}px`;
});
});
ポイント解説
- デザインの工夫
- CSS変数で色を管理し、カスタマイズを容易に
- 十分な余白でコンテンツの可読性を向上
- ホバー時の視覚的フィードバック
- アニメーションの特徴
- プラス/マイナスアイコンの滑らかな変形
- コンテンツの高さを正確に計算
- リサイズ時の高さ自動調整
- 保守性の向上
- アニメーションのタイミングを変数で管理
- リサイズ対応で様々な画面サイズに対応
すみません、同じようなパターンでさらに見出し5を書いていきます。
4.タブ切り替え連動アコーディオン
次はタブメニューと連動して動作する、より実用的なアコーディオンメニューのパターンです。
See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.
この実装の特徴は以下の通りです。
- タブ切り替えと連動した表示制御
- スムーズな開閉アニメーション
- コンテンツの高さに応じた自動調整
HTML
<div class="tab-accordion">
<!-- タブメニュー -->
<div class="tab-nav">
<button class="tab-button active" data-target="tab1">タブ1</button>
<button class="tab-button" data-target="tab2">タブ2</button>
<button class="tab-button" data-target="tab3">タブ3</button>
</div>
<!-- アコーディオン -->
<div class="tab-content active" id="tab1">
<button class="tab-trigger" aria-expanded="false">
<span>アコーディオン1</span>
<svg class="tab-icon" width="24" height="24" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"></path>
</svg>
</button>
<div class="accordion-content">
<div class="accordion-body">
タブ1のコンテンツがここに入ります。
アコーディオンの開閉と連動して表示されます。
</div>
</div>
</div>
<div class="tab-content" id="tab2">
<button class="tab-trigger" aria-expanded="false">
<span>アコーディオン2</span>
<svg class="tab-icon" width="24" height="24" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"></path>
</svg>
</button>
<div class="accordion-content">
<div class="accordion-body">
タブ2のコンテンツです。
別のタブに切り替えると自動的に閉じます。
</div>
</div>
</div>
<div class="tab-content" id="tab3">
<button class="tab-trigger" aria-expanded="false">
<span>アコーディオン3</span>
<svg class="tab-icon" width="24" height="24" viewBox="0 0 24 24">
<path d="M6 9l6 6 6-6"></path>
</svg>
</button>
<div class="accordion-content">
<div class="accordion-body">
タブ3のコンテンツです。
各タブで独立してアコーディオンが動作します。
</div>
</div>
</div>
</div>
CSS
.tab-accordion {
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.tab-nav {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.tab-button {
padding: 0.75rem 1.5rem;
background: #f1f5f9;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.2s;
}
.tab-button:hover {
background: #e2e8f0;
}
.tab-button.active {
background: #3b82f6;
color: white;
}
.tab-content {
display: none;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
overflow: hidden;
}
.tab-content.active {
display: block;
}
.tab-trigger {
width: 100%;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
background: white;
border: none;
cursor: pointer;
}
.tab-icon {
transition: transform 0.2s;
}
.tab-trigger[aria-expanded="true"] .tab-icon {
transform: rotate(180deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.accordion-content.open {
max-height: 200px;
}
.accordion-body {
padding: 1rem;
border-top: 1px solid #e2e8f0;
}
JavaScript
// タブ切り替えの処理
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
// アクティブなタブを切り替え
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
button.classList.add('active');
// タブコンテンツを切り替え
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
const targetId = button.dataset.target;
document.getElementById(targetId).classList.add('active');
// アコーディオンを全て閉じる
document.querySelectorAll('.tab-trigger').forEach(trigger => {
trigger.setAttribute('aria-expanded', 'false');
trigger.nextElementSibling.classList.remove('open');
});
});
});
// アコーディオンの処理
document.querySelectorAll('.tab-trigger').forEach(trigger => {
trigger.addEventListener('click', () => {
const content = trigger.nextElementSibling;
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
trigger.setAttribute('aria-expanded', !isExpanded);
content.classList.toggle('open');
});
});
コードの解説
- HTML構造
- タブメニューとアコーディオンを組み合わせた階層構造
- data-target属性でタブとコンテンツを紐付け
- アクセシビリティを考慮した属性設定
- CSSの特徴
- flexboxを使用したタブレイアウト
- 状態に応じた表示/非表示の切り替え
- スムーズな開閉アニメーション
- JavaScriptのポイント
- タブ切り替え時のアコーディオンリセット
- 独立した状態管理による安定した動作
- 複数の要素を連動させた制御
さいごに
この記事では、実践的なアコーディオンメニューの実装について4つのパターンを解説しました。
それぞれのコードはコピー&ペーストですぐに使え、カスタマイズも容易な設計になっています。実際のプロジェクトでもすぐに活用できます。
また、アクセシビリティにも配慮した実装を心がけていますので、より多くのユーザーに使いやすいUIを提供できますのでぜひ参考にしてみてください。