<!-- 1. aria属性を使った書き方 -->
<div class="Accordion">
<button class="Accordion__Summary" data-accordion aria-controls="some-id" aria-expanded="false">
<span>1. div, button, aria属性を使用して実装したアコーディオン</span>
</button>
<div class="Accordion__Content" id="some-id" aria-hidden="true">
<p>開閉状態は aria-expanded の値で判定します。</p>
</div>
</div>
<!-- 2. details, summary を使った書き方 -->
<details class="Accordion">
<summary class="Accordion__Summary">
<span>2. details, summaryを使用したアコーディオン</span>
</summary>
<div class="Accordion__Content">
<p>開閉状態は details タグの open 属性の有無で判定します。</p>
</div>
</details>
* {
box-sizing: border-box;
}
// クリック可能要素をホバーしたときにカーソルをポインターにする
// 1.の場合button, 2.の場合summary
button,
summary {
cursor: pointer;
}
// 1.の場合、buttonのスタイルリセット
button {
border: none;
}
// 2.の場合、summaryの「▼」をリセット
summary {
list-style: none;
&::details-marker {
display: none;
}
}
.Accordion {
margin: 20px;
border: 2px solid #ccf;
}
.Accordion__Summary {
width: 100%;
font-size: 18px;
position: relative;
padding: 20px;
background-color: #ccf;
color: #33c;
text-align: left;
// 「+」「-」の装飾
&::before,
&::after {
position: absolute;
top: 50%;
display: block;
width: 16px;
height: 2px;
content: "";
background-color: currentColor;
right: 30px;
}
&::before {
transform: translateY(-50%);
}
&::after {
transition: transform 0.5s;
transform: translateY(-50%) rotate(-90deg);
}
// 1. の場合
&[aria-expanded="true"]::after {
transform: translateY(-50%);
}
// 2. の場合
.Accordion[open]:not([data-accordion-before-close]) &::after {
transform: translateY(-50%);
}
}
.Accordion__Content {
overflow: hidden;
> p {
padding: 20px;
}
}
View Compiled
import gsap from "https://cdn.skypack.dev/gsap@3.10.4";
// 1. aria属性を使った書き方
document.querySelectorAll("[data-accordion]").forEach((button) => {
const content = document.getElementById(button.getAttribute("aria-controls"));
const onClick = () => {
if (button.getAttribute("aria-expanded") === "false") {
button.setAttribute("aria-expanded", true);
content.setAttribute("aria-hidden", false);
gsap.fromTo(
content,
{ height: 0, clearProps: "display" },
{ height: "auto" }
);
} else {
button.setAttribute("aria-expanded", false);
content.setAttribute("aria-hidden", true);
gsap.to(content, { height: 0, display: "none" });
}
};
button.addEventListener("click", onClick);
// 初期化
if (button.getAttribute("aria-expanded") === "false") {
content.setAttribute("aria-hidden", true);
gsap.set(content, { height: 0, display: "none" });
}
});
// 2. details, summary を使った書き方
// ※アニメーションが必要なければ、JSは不要
document.querySelectorAll("details").forEach((details) => {
const summary = details.querySelector("summary");
const content = details.querySelector("summary + *");
const onClick = (event) => {
if (details.open) {
// 閉じるアニメーションのみ preventDefault が必要
event.preventDefault();
details.setAttribute("data-accordion-before-close", "");
gsap.to(content, {
height: 0,
onComplete: () => {
details.open = false;
details.removeAttribute("data-accordion-before-close");
},
});
} else {
gsap.fromTo(content, { height: 0 }, { height: "auto" });
}
};
summary.addEventListener("click", onClick, { passive: false });
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.