WebサイトやアプリケーションのQ&Aセクションなどでよく見かけるアコーディオンメニュー。
今回は、details
タグとsummary
タグを使用して実装していきます。コピペOKなアコーディオンメニューになっていますので、ぜひご活用ください。
実装するコード
早速ですが、今回ご紹介するアコーディオンメニューの実装内容になります。details
タグとsummary
タグを活用し、一つのコンテンツを開いた際に、他のコンテンツは閉じるように実装しています。
See the Pen アコーディオンメニュー_1つ開いたら1つ閉じるver by moreo (@moreoweb) on CodePen.
各コードの詳細な解説は順を追って説明していきます。
まずHTMLのコードの説明を行い、その後CSSとJavaScriptのセクションで、スタイリングとアニメーションの実装方法を解説していきます。
HTMLのみで作るシンプルなアコーディオンメニュー
details
タグとsummary
タグを使用することで、JavaScriptや追加のCSSなしでアコーディオンの開閉動作を簡単に実現できます!
以下のコードは、details
タグを使って質問部分を表し、その中にsummary
タグで質問のタイトルを、div
タグで回答の内容を配置しています。
このようにHTMLだけで簡単にアコーディオンメニューを作成することができます。
See the Pen 【 HTMLのみ 】アコーディオンメニュー by moreo (@moreoweb) on CodePen.
コード一覧
HTML
<section class="faq ">
<div class="inner">
<h2 class="title">よくある質問</h2>
<div class="faq__contents">
<details class="faq__details">
<summary class="faq__summary">
質問1
</summary>
<div class="faq__content">
<p class="faq__text">回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。</p>
</div>
</details>
<details class="faq__details">
<summary class="faq__summary">
質問2
</summary>
<div class="faq__content">
<p class="faq__text"> 回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。</p>
</div>
</details>
<details class="faq__details">
<summary class="faq__summary">
質問3
</summary>
<div class="faq__content">
<p class="faq__text">回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。</p>
</div>
</details>
</div>
</div>
</section>
CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.6;
}
.faq {
margin-top: 80px;
margin-bottom: 80px;
}
.inner {
max-width: 900px;
padding: 0 4%;
margin: 0 auto;
}
CSSを用いてスタイリング
デフォルトで表示されている三角形アイコンを消してしまいます。
よくある質問ということで、QとAをテキストの前に配置しました。
See the Pen CSS アコーディオンメニュー by moreo (@moreoweb) on CodePen.
コード一覧
HTML
<section class="faq ">
<div class="inner">
<h2 class="title">よくある質問</h2>
<div class="faq__contents">
<details class="faq__details js-details">
<summary class="faq__summary js-summary">
質問1<span class="arrow-icon"></span>
</summary>
<div class="faq__content js-content">
<p class="faq__text">回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。</p>
</div>
</details>
<details class="faq__details js-details">
<summary class="faq__summary js-summary">
質問2<span class="arrow-icon"></span>
</summary>
<div class="faq__content js-content">
<p class="faq__text"> 回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。</p>
</div>
</details>
<details class="faq__details js-details">
<summary class="faq__summary js-summary">
質問3<span class="arrow-icon"></span>
</summary>
<div class="faq__content js-content">
<p class="faq__text">回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。</p>
</div>
</details>
</div>
</div>
</section>
CSS
デフォルトの三角形アイコンを非表示にします。
JavaScriptによるクリックイベントが発生した際に、active
クラスを適用させ、矢印を180度回転させています。
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.6;
}
summary {
display: block;
}
/* デフォルトの三角形アイコンを非表示にする */
summary::-webkit-details-marker {
display: none;
}
.faq {
margin-top: 80px;
margin-bottom: 80px;
}
.inner {
max-width: 900px;
padding: 0 4%;
margin: 0 auto;
}
.faq__contents {
margin-top: 24px;
}
.faq__details {
padding: 16px;
}
.faq__summary {
cursor: pointer;
position: relative;
display: flex;
justify-content: space-between;
padding-left: 32px;
}
.faq__summary::before,
.faq__text::before {
position: absolute;
left: 0;
height: 24px;
width: 24px;
border-radius: 100px;
text-align: center;
display: block;
}
.faq__summary::before {
content: "Q";
background-color: #2f3135;
color: #ffffff;
top: 0;
}
.faq__text::before {
content: "A";
background-color: #fd952e;
color: #ffffff;
top: 50%;
transform: translateY(-50%);
}
/* 矢印アイコン */
.arrow-icon {
display: inline-block;
position: relative;
transition: transform 0.4s ease;
height: 16px;
width: 16px;
transform: rotate(135deg);
}
.arrow-icon::before {
content: "";
width: 10px;
height: 10px;
border-top: solid 2px #2f3135;
border-right: solid 2px #2f3135;
position: absolute;
top: 50%;
right: 50%;
}
/* アクティブな状態の矢印アイコン */
.arrow-icon.active {
transform: rotate(315deg);
}
.faq__text {
padding: 16px 32px;
position: relative;
}
.faq__content {
overflow: hidden;
}
JavaScriptコード
クリックした際に、矢印アイコン(.arrow-icon
)にactive
クラスを追加または削除します。これにより、CSSで定義された回転アニメーションが適用され、矢印アイコンの向きが変わります。
document.querySelectorAll(".faq__details").forEach((details) => {
const arrowIcon = details.querySelector(".arrow-icon");
details.querySelector(".faq__summary").addEventListener("click", () => {
arrowIcon.classList.toggle("active");
});
});
JavaScriptで滑らかなアニメーションに
アコーディオンメニューの開閉動作を滑らかにするためのJavaScriptを実装していきます。
See the Pen アコーディオンメニュー_1つ開いたら1つ閉じるver by moreo (@moreoweb) on CodePen.
コード一覧
HTML
<section class="faq ">
<div class="inner">
<h2 class="title">よくある質問</h2>
<div class="faq__contents">
<details class="faq__details js-details">
<summary class="faq__summary js-summary">
質問1<span class="arrow-icon"></span>
</summary>
<div class="faq__content js-content">
<p class="faq__text">回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。回答1テキストの部分です。</p>
</div>
</details>
<details class="faq__details js-details">
<summary class="faq__summary js-summary">
質問2<span class="arrow-icon"></span>
</summary>
<div class="faq__content js-content">
<p class="faq__text"> 回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。回答2テキストの部分です。</p>
</div>
</details>
<details class="faq__details js-details">
<summary class="faq__summary js-summary">
質問3<span class="arrow-icon"></span>
</summary>
<div class="faq__content js-content">
<p class="faq__text">回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。回答3テキストの部分です。</p>
</div>
</details>
</div>
</div>
</section>
CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.6;
}
summary {
display: block;
}
summary::-webkit-details-marker {
display: none;
}
.faq {
margin-top: 80px;
margin-bottom: 80px;
}
.inner {
max-width: 900px;
padding: 0 4%;
margin: 0 auto;
}
.faq__contents {
margin-top: 24px;
}
.faq__details {
padding: 16px;
}
.faq__summary {
cursor: pointer;
position: relative;
display: flex;
justify-content: space-between;
padding-left: 32px;
}
.faq__summary::before,
.faq__text::before {
position: absolute;
left: 0;
height: 24px;
width: 24px;
border-radius: 50%;
text-align: center;
display: block;
}
.faq__summary::before {
content: "Q";
background-color: #2f3135;
color: #ffffff;
top: 0;
}
.faq__text::before {
content: "A";
background-color: #fd952e;
color: #ffffff;
top: 50%;
transform: translateY(-50%);
}
/* 矢印アイコン */
.arrow-icon {
display: inline-block;
position: relative;
transition: transform 0.4s ease;
height: 16px;
width: 16px;
transform: rotate(135deg);
}
.arrow-icon::before {
content: "";
width: 10px;
height: 10px;
border-top: solid 2px #2f3135;
border-right: solid 2px #2f3135;
position: absolute;
top: 50%;
right: 50%;
}
/* アクティブな状態の矢印アイコン */
.arrow-icon.active {
transform: rotate(315deg);
}
.faq__text {
padding: 16px 32px;
position: relative;
}
.js-details[open] .js-content {
height: auto;
}
.faq__content {
overflow: hidden;
height: 0;
transition: height 0.4s ease;
}
JavaScript
opacity
(透明度)とheight
(高さ)のプロパティを使用し、要素がスムーズに表示されるように実装しています。
const animTiming = {
duration: 400,
easing: "ease-out"
};
// アニメーションキーフレームの生成
const animKeyframes = (content, isOpening) => [
{
opacity: isOpening ? 0 : 1,
height: isOpening ? 0 : content.scrollHeight + "px"
},
{
opacity: isOpening ? 1 : 0,
height: isOpening ? content.scrollHeight + "px" : 0
}
];
const detailsElements = document.querySelectorAll(".faq__details");
detailsElements.forEach((details) => {
const summary = details.querySelector(".faq__summary");
const content = details.querySelector(".faq__content");
const arrowIcon = summary.querySelector(".arrow-icon");
// summary要素のクリックイベントリスナー
summary.addEventListener("click", (event) => {
event.preventDefault(); // デフォルトの開閉動作を防止
const isOpening = !details.open; // 現在開いているかどうかをチェック
if (isOpening) {
details.open = true; // アコーディオンを開く
requestAnimationFrame(() => {
// アニメーションの実行
content.animate(animKeyframes(content, isOpening), animTiming);
});
} else {
const animation = content.animate(
animKeyframes(content, isOpening),
animTiming
);
// アニメーション完了時の処理
animation.onfinish = () => {
details.open = false; // アコーディオンを閉じる
};
}
// 矢印アイコンのクラス切り替え
arrowIcon.classList.toggle("active");
});
});
最終的なコード
最後に、一つのコンテンツが開かれたときに他のコンテンツが自動的に閉じるようにするJavaScriptのコードを実装します
See the Pen アコーディオンメニュー_1つ開いたら1つ閉じるver by moreo (@moreoweb) on CodePen.
実装コード
一つのコンテンツを開いた際に、他のコンテンツは閉じるためのコードを追記したJavaScriptのコードのみ記載します。
const animTiming = {
duration: 400,
easing: "ease-out"
};
const animKeyframes = (content, isOpening) => [
{
opacity: isOpening ? 0 : 1,
height: isOpening ? 0 : content.scrollHeight + "px"
},
{
opacity: isOpening ? 1 : 0,
height: isOpening ? content.scrollHeight + "px" : 0
}
];
const detailsElements = document.querySelectorAll(".faq__details");
detailsElements.forEach((details) => {
const summary = details.querySelector(".faq__summary");
const content = details.querySelector(".faq__content");
const arrowIcon = summary.querySelector(".arrow-icon");
summary.addEventListener("click", (event) => {
event.preventDefault();
const isOpening = !details.open; // 現在開いているかどうかをチェック
// 他のアコーディオンを閉じる処理
detailsElements.forEach((otherDetails) => {
if (otherDetails !== details && otherDetails.open) {
otherDetails.open = false; // 他の要素を閉じる
otherDetails.querySelector(".arrow-icon").classList.remove("active");
}
});
if (isOpening) {
details.open = true;
requestAnimationFrame(() => {
content.animate(animKeyframes(content, isOpening), animTiming);
});
} else {
const animation = content.animate(
animKeyframes(content, isOpening),
animTiming
);
animation.onfinish = () => {
details.open = false;
};
}
arrowIcon.classList.toggle("active");
});
});
まとめ
この記事では、JavaScriptを使ってスムーズに開閉するアコーディオンメニューの実装方法について解説しました。
まず、HTMLのdetails
とsummary
タグを用いて基本的な構造を作成し、CSSでスタイリング。
次に、JavaScriptを活用して、アコーディオンの開閉に滑らかなアニメーションを実装し、一つのコンテンツが開かれたときに他のコンテンツが閉じるように実装しました。
ご自身の状況に合わせてぜひご活用ください!