DetailsでSlideToggleぽい動きのアコーディオンを作ってみる

目次

解説

VanillaJsでJqueryのslideToggleのような動きをさせる方法を紹介します。

例:SlideToggleは以下のような動きのことを指します。

See the Pen Accordion2 by gsg0222 (@gsg0222) on CodePen.



アクセシビリティも考慮してDetailsSummaryを使用します。

Can I useでもほぼ全てのブラウザでサポートされていますので安心して使用できます。

button部分は -のアイコンのためのコードを記述しているためやや長めですが、コピペで使用できます。

注意点としてはPreflightfalseにしているとbeforeのcontentが効かないため、Preflighttrueにするか、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();
    });
  });

デモ

詳しく見る

アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容

詳しく見る

アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容
アコーディオン内容

参考

qiita.com/kiita_chi/items/c9d7d20beb82dd279c8e