コピペで完結!アコーディオン#03【複数同時オープン対応】
html/css/js
【複数同時オープン対応】いくつでも開けるタイプ
こちらは複数のアコーディオン項目を同時に開けるタイプです。
「FAQ」や「Q&A」など、関連情報を並べて確認したい時に最適です。
JavaScriptで高さを制御し、スムーズなスライド表示と矢印アイコンの回転アニメーションを実現しています。
コードについて
本記事のコードは AI(ChatGPT)による生成をベースに作成・調整しています。ご利用の環境でテストの上ご使用ください。
免責
本コードの利用に伴う不具合・損害について、当サイトは責任を負いません。自己責任にてご利用ください。
デモ
開閉時に矢印アイコンが回転し、スライドアニメーションで本文を表示します。FAQやQ&Aページに最適です。
このデモはJSで開閉を制御しています。矢印回転やスライド量を安定的に管理でき、複雑なレイアウトでも破綻しにくいのが利点です。
はい。同時開きに対応しています。単一開きにしたい場合は、スクリプト内のコメントを参照して切り替えてください。
コードをコピーして使おう!
/* =========================================
1. 外枠全体のスタイル設定
-----------------------------------------
アコーディオン全体を包むボックス(ID: #accordion-section)に
枠線・角丸・背景などを設定します。
========================================= */
#accordion-section{
border:1px solid #e5e7eb; /* 外枠の薄いグレー線 */
border-radius:10px; /* 角をやや丸めて柔らかい印象に */
overflow:hidden; /* 内部のはみ出しを隠す(スライド中に効果) */
background:#fff; /* 背景色は白 */
}
/* =========================================
2. 各項目(.item)の区切り線
-----------------------------------------
各質問・回答ペアを区切る下線を追加。
========================================= */
#accordion-section .item{
border-bottom:1px solid #e5e7eb; /* 下境界線を追加 */
}
/* =========================================
3. 見出しラッパー(.accordion-title)
========================================= */
#accordion-section .accordion-title{
margin:0; /* デフォルトの余白を削除(整列を安定) */
}
/* =========================================
4. トグルボタン(.toggle)
-----------------------------------------
開閉を操作するボタン部分。
flexで左右に「質問テキスト」と「▼アイコン」を配置。
ホバー・フォーカス時の視認性も考慮。
========================================= */
#accordion-section .toggle{
display:flex; /* 横並び配置 */
align-items:center; /* 縦中央揃え */
justify-content:space-between; /* 左右両端配置 */
width:100%; /* 幅いっぱいに拡張 */
background:#f8fafc; /* ごく淡いグレー背景 */
border:none; /* ボーダーを除去 */
padding:14px 16px; /* クリックしやすい余白 */
cursor:pointer; /* ホバー時にポインタ表示 */
font-weight:600; /* 見出しとして太字 */
text-align:left; /* 文字を左寄せ */
transition:background .25s ease; /* 背景色変更を滑らかに */
}
/* ホバー時の視覚反応(少し青みがかった背景) */
#accordion-section .toggle:hover{
background:#eef2ff;
}
/* キーボード操作でフォーカスされた時に強調表示 */
#accordion-section .toggle:focus-visible{
outline:2px solid #0b6bff; /* 青いアウトライン */
outline-offset:2px; /* 少し外側に配置 */
}
/* =========================================
5. 矢印アイコン(.icon)
-----------------------------------------
ボタン右端の▼マークを回転させることで、
開閉状態を直感的に伝える。
========================================= */
#accordion-section .icon{
transition:transform .3s ease; /* 回転を滑らかに */
}
/* aria-expanded="true" のとき矢印を180度回転(▼→▲) */
#accordion-section .toggle[aria-expanded="true"] .icon{
transform:rotate(180deg);
}
/* =========================================
6. 回答パネル(.panel)
-----------------------------------------
高さをJSで操作してスライドアニメーションを実現。
初期状態は非表示(height:0)。
========================================= */
#accordion-section .panel{
overflow:hidden; /* アニメ中に内容がはみ出さないように */
height:0; /* 初期状態では閉じる */
transition:height .28s ease; /* 開閉のスピード・滑らかさを指定 */
will-change:height; /* GPUに高さアニメーションを最適化させる */
}
/* =========================================
7. 内部コンテンツ(.panel-inner)
-----------------------------------------
実際のテキストを包む要素。
表示時はフェードイン+軽く下から上がる動きを付与。
========================================= */
#accordion-section .panel-inner{
padding:16px; /* 文字の内側余白 */
line-height:1.8; /* 行間をゆったり */
background:#fff; /* 背景は白のまま */
opacity:0; /* 初期は透明(非表示) */
transform:translateY(-2px); /* わずかに上へずらしてから登場 */
transition:opacity .24s ease, transform .24s ease; /* フェード+移動 */
}
/* 開いた状態のパネル(data-open="true")に適用 */
#accordion-section .panel[data-open="true"] .panel-inner{
opacity:1; /* フェードイン */
transform:translateY(0); /* 元の位置に戻る */
}
/* =========================================
8. 動きを控える設定への対応
-----------------------------------------
OSの設定で「動きを減らす」が有効な場合、
すべてのアニメーションをオフに。
========================================= */
@media (prefers-reduced-motion: reduce){
#accordion-section .panel,
#accordion-section .panel-inner,
#accordion-section .icon{
transition:none; /* トランジションを無効化 */
}
}
<!-- ===============================================
▼HTML:.accordion-title採用版(h3非使用)
=============================================== -->
<div id="accordion-section">
<div class="item">
<div class="accordion-title" role="heading" aria-level="3">
<button class="toggle" type="button"
aria-expanded="false"
aria-controls="panel-1" id="btn-1">
<span>Q1. このアコーディオンの特徴は?</span>
<span class="icon" aria-hidden="true">▼</span>
</button>
</div>
<div class="panel" id="panel-1" role="region"
aria-labelledby="btn-1" data-open="false">
<div class="panel-inner">
開閉時に矢印アイコンが回転し、スライドアニメーションで自然に表示されます。
</div>
</div>
</div>
<div class="item">
<div class="accordion-title" role="heading" aria-level="3">
<button class="toggle" type="button"
aria-expanded="false"
aria-controls="panel-2" id="btn-2">
<span>Q2. JavaScriptは必要?</span>
<span class="icon" aria-hidden="true">▼</span>
</button>
</div>
<div class="panel" id="panel-2" role="region"
aria-labelledby="btn-2" data-open="false">
<div class="panel-inner">
このバージョンではJSで開閉を制御します。ブラウザ差のない安定した挙動が特長です。
</div>
</div>
</div>
<div class="item">
<div class="accordion-title" role="heading" aria-level="3">
<button class="toggle" type="button"
aria-expanded="false"
aria-controls="panel-3" id="btn-3">
<span>Q3. 複数同時に開ける?</span>
<span class="icon" aria-hidden="true">▼</span>
</button>
</div>
<div class="panel" id="panel-3" role="region"
aria-labelledby="btn-3" data-open="false">
<div class="panel-inner">
はい。複数同時開きが可能です。単一開きに変更する場合はJS側の設定で切り替えます。
</div>
</div>
</div>
</div>
// ======================================================
// ▼JavaScript:アコーディオン制御スクリプト
// ------------------------------------------------------
// 目的:ボタンをクリックすると、回答パネルがスライドで開閉する。
// 矢印アイコンが回転して状態を示す。
// CSSで高さアニメーションを制御するため、JSは高さ計算のみ担当。
// ======================================================
(() => {
// ----------------------------------------------------
// ① ルート要素を取得
// ----------------------------------------------------
const root = document.querySelector('#accordion-section');
if(!root) return; // 対象要素がなければ即終了
// ----------------------------------------------------
// ② 各アコーディオン項目(.item)を配列化
// ----------------------------------------------------
const items = Array.from(root.querySelectorAll('.item'));
// ----------------------------------------------------
// ③ 単一開きモード設定
// ----------------------------------------------------
// true にすると常に1つだけ開く状態になる。
// false のままだと複数の項目を同時に開ける。
const SINGLE_OPEN = false;
// ----------------------------------------------------
// ④ パネルを開く関数
// ----------------------------------------------------
// 内容の高さ(scrollHeight)を取得してheightを設定、
// トランジション完了後にheight:autoに戻す事で、
// 再開閉やリサイズにも柔軟に対応。
const openPanel = (panel, btn) => {
// 現在の高さをscrollHeightに設定して開くアニメーションを開始
panel.style.height = panel.scrollHeight + 'px';
panel.setAttribute('data-open','true');
btn.setAttribute('aria-expanded','true'); // 開いたことをARIAで通知
// アニメーション完了時にheightをautoに変更(柔軟な伸縮に対応)
const onEnd = (e) => {
if(e.propertyName !== 'height') return; // height以外の変化は無視
panel.style.height = 'auto';
panel.removeEventListener('transitionend', onEnd);
};
panel.addEventListener('transitionend', onEnd);
};
// ----------------------------------------------------
// ⑤ パネルを閉じる関数
// ----------------------------------------------------
// 現在の高さから0に向けてアニメーションすることで
// スライドアップのような閉じる動きを再現。
const closePanel = (panel, btn) => {
panel.style.height = panel.scrollHeight + 'px'; // 現在高さを固定
// requestAnimationFrameで次の描画タイミングに変更を実行(滑らか)
requestAnimationFrame(() => panel.style.height = '0px');
panel.setAttribute('data-open','false');
btn.setAttribute('aria-expanded','false'); // 閉じた状態を通知
};
// ----------------------------------------------------
// ⑥ 各ボタン(.toggle)にクリックイベントを付与
// ----------------------------------------------------
items.forEach((item) => {
const btn = item.querySelector('.toggle'); // 質問ボタン
const panel = item.querySelector('.panel'); // 回答領域
// 初期状態でパネルを閉じておく(height:0)
panel.style.height = '0px';
// クリック時の処理
btn.addEventListener('click', () => {
// 現在開いているかどうかを判定
const isOpen = btn.getAttribute('aria-expanded') === 'true';
// --- 単一開きモード(SINGLE_OPEN) ---
// 他の項目が開いている場合、それらを閉じる
if(SINGLE_OPEN && !isOpen){
items.forEach((it) => {
if(it === item) return; // 今クリックしたものは除外
const b = it.querySelector('.toggle');
const p = it.querySelector('.panel');
if(b.getAttribute('aria-expanded') === 'true') closePanel(p,b);
});
}
// --- 開閉トグル処理 ---
if(isOpen){
// すでに開いている → 閉じる
closePanel(panel, btn);
}else{
// 閉じている → 開く
panel.style.height = '0px'; // 前回autoの場合に備えて初期化
requestAnimationFrame(() => openPanel(panel, btn));
}
});
});
// ----------------------------------------------------
// ⑦ ウィンドウリサイズ時の調整処理
// ----------------------------------------------------
// 開いているパネル(height:auto)を維持するために、
// アニメーション中の高さを再計算して反映する。
window.addEventListener('resize', () => {
items.forEach((item) => {
const btn = item.querySelector('.toggle');
const panel = item.querySelector('.panel');
// 開いている状態かつ、まだautoになっていない場合のみ再計算
if(btn.getAttribute('aria-expanded') === 'true' && panel.style.height !== 'auto'){
panel.style.height = panel.scrollHeight + 'px';
}
});
});
})(); // 即時実行関数(他スクリプトとの干渉防止)
コピペで完結!アコーディオン#03【複数同時オープン対応】
コメント