DetailsでSlideToggleぽい動きのアコーディオンを作ってみる
目次
解説
VanillaJsでJqueryのslideToggleのような動きをさせる方法を紹介します。
例:SlideToggleは以下のような動きのことを指します。
See the Pen Accordion2 by gsg0222 (@gsg0222) on CodePen.
アクセシビリティも考慮してDetailsとSummaryを使用します。
Can I useでもほぼ全てのブラウザでサポートされていますので安心して使用できます。
button部分は+ -のアイコンのためのコードを記述しているためやや長めですが、コピペで使用できます。
注意点としてはPreflightをfalseにしているとbeforeのcontentが効かないため、Preflightをtrueにするか、contentを['']にする必要があります。
DetailsのアコーディオンをSlideToggleにするには、gridのgrid-rowsを使って高さを変更することで実現できます。
grid-rows-[0fr]→grid-rows-[1fr]に変更することで高さを変更しています。
またページ内検索でアコーディオンが開くためアクセシビリティを考慮したアコーディオンが作成できます。
<details class="js-details group">
<summary
class="js-details-header flex items-center justify-between bg-[#ececec] p-8"
>
<span>詳しく見る</span>
<button
class="text-blabg-black relative inline-block h-[2px] min-w-4 bg-black align-middle leading-none before:absolute before:left-[0] before:top-[0] before:h-full before:w-full before:rotate-90 before:content-[''] before:[background:inherit] before:[border-radius:inherit] before:[transition:0.3s] group-open:before:rotate-0"
></button>
</summary>
<div
class="js-details-content grid grid-rows-[0fr] transition-all duration-500 group-open:grid-rows-[1fr]"
>
<div class="overflow-hidden">
<p class="p-8">
アコーディオン内容<br />
アコーディオン内容<br />
アコーディオン内容<br />
アコーディオン内容<br />
アコーディオン内容<br />
アコーディオン内容<br />
アコーディオン内容<br />
アコーディオン内容<br />
</p>
</div>
</div>
</details>
JavaScriptは以下のようになります。
Detailsのopen属性を使って開閉を判定しています。
Detailsのopenを素のままで使用すると、即座に開閉されてしまうため、requestAnimationFrameを使って開閉時のアニメーションを実現しています。
またisTransitioningフラグを使用して連打防止をしています。
const details = [
...document.querySelectorAll(".js-details"),
];
let isTransitioning = false; // 連打防止フラグ
details.map((el) => {
const accordionHeader = el.querySelector(".js-details-header");
const accordionContent = el.querySelector(
".js-details-content",
);
if (!accordionHeader || !accordionContent) return;
const onOpen = async () => {
if (el.open || isTransitioning) {
return;
}
isTransitioning = true;
accordionContent.style.gridTemplateRows = "0fr";
el.setAttribute("open", "");
requestAnimationFrame(() => {
requestAnimationFrame(() => {
accordionContent.style.gridTemplateRows = "1fr";
});
});
accordionContent.addEventListener(
"transitionend",
() => {
isTransitioning = false;
},
{ once: true },
);
};
const onClose = () => {
if (!el.open || isTransitioning) {
return;
}
isTransitioning = true;
accordionContent.style.gridTemplateRows = "0fr";
accordionContent.addEventListener(
"transitionend",
() => {
el.removeAttribute("open");
accordionContent.style.gridTemplateRows = "";
isTransitioning = false;
},
{ once: true },
);
};
accordionHeader.addEventListener("click", (e) => {
e.preventDefault();
if (!el.open) {
onOpen();
}
onClose();
});
});
デモ
詳しく見る
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
詳しく見る
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容