<!-- ※ Chrome 129以上で動作 -->
<details class="accordion js-accordion">
  <summary class="summary">
    <h3 class="heading"> EVOLABとは <span class="icon" role="img" aria-hidden="true"></span> </h3>
  </summary>
  <div class="panel">
    <div class="panel-inner">
      <p>EVOWORXのエンジニアによるWeb制作会社向けの情報ポータルです。</p>
    </div>
  </div>
</details>
<details class="accordion js-accordion">
  <summary class="summary">
    <h3 class="heading"> BLOG <span class="icon" role="img" aria-hidden="true"></span> </h3>
  </summary>
  <div class="panel">
    <div class="panel-inner">
      <p>EVOWORXのエンジニアによるブログをお届けします。</p>
    </div>
  </div>
</details>
<details class="accordion js-accordion">
  <summary class="summary">
    <h3 class="heading"> SAMPLE <span class="icon" role="img" aria-hidden="true"></span> </h3>
  </summary>
  <div class="panel">
    <div class="panel-inner">
      <p>コピー&amp;ペーストで使えるコンポーネントのサンプルです。</p>
    </div>
  </div>
</details>
<details class="accordion js-accordion">
  <summary class="summary">
    <h3 class="heading"> LAB <span class="icon" role="img" aria-hidden="true"></span> </h3>
  </summary>
  <div class="panel">
    <div class="panel-inner">
      <p>アニメーションなどのコードの実験室です。</p>
    </div>
  </div>
</details>
<details class="accordion js-accordion">
  <summary class="summary">
    <h3 class="heading"> GUIDELINE &amp; TIPS <span class="icon" role="img" aria-hidden="true"></span> </h3>
  </summary>
  <div class="panel">
    <div class="panel-inner">
      <p>弊社のコーディングガイドラインやWeb制作のお役立ち情報です。</p>
    </div>
  </div>
</details>
.accordion {
  background-color: #fff;
  border-radius: 10px;
  padding: 20px;

  &:not(:first-of-type) {
    margin-top: 30px;
  }

  &.is-open {
    .heading {
      .icon {
        &::before {
          rotate: 180deg;
        }
      }
    }

    .panel {
      // フォールバック
      height: auto;
      height: calc-size(auto, size);
    }
  }

  .heading {
    width: 100%;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: 12px;
    font-weight: 700;

    &::before {
      content: "Q";
      width: 35px;
      display: grid;
      place-content: center;
      aspect-ratio: 1;
      background-color: #333;
      border-radius: 50%;
      color: #fff;
      font-size: 20px;
      font-weight: 700;
      position: relative;
    }

    .icon {
      width: 16px;
      aspect-ratio: 1;
      position: relative;

      &::before,
      &::after {
        content: "";
        display: block;
        width: 100%;
        height: 2px;
        background-color: #333;
        position: absolute;
        inset: 0;
        margin: auto;
        border-radius: 16px;
      }

      &::before {
        rotate: 90deg;
        transition: rotate 0.3s ease;
      }
    }
  }

  .panel {
    display: block;
    overflow: clip;
    height: 0;
    transition: height 0.4s ease;
    
    @media (scripting: none) {
      height: auto;
    }

    .panel-inner {
      padding-top: 32px;
      padding-bottom: 4px;
    }
  }
}

:root {
  interpolate-size: allow-keywords;
}

body {
  background-color: #333;
  padding: 30px;
}

summary {
  display: block;
  cursor: pointer;

  &::-webkit-details-marker {
    display: none;
  }
}
View Compiled
class Accordion {
  constructor(el) {
    // DOM要素
    this.details = el;
    if (!this.details) return;
    this.trigger = null;
    this.panel = null;
    // インスタンス
    this.observer = null;
    // フラグ
    this.isOpen = null;
    this.isAnimating = false;
  }

  // 初期化
  init() {
    this.trigger = this.details.querySelector("summary");
    this.panel = this.trigger.nextElementSibling;
    this.isOpen = this.details.open;
    this._observeHidden();
    this.trigger.addEventListener("click", this._toggle.bind(this));
  }

  // 破棄
  destroy() {
    if (this.trigger) {
      this.trigger.removeEventListener("click", this._toggle.bind(this));
      this.trigger = null;
    }
    if (this.panel) {
      this.panel = null;
    }
    if (this.observer) {
      this.observer.disconnect();
      this.observer = null;
    }
  }

  // アニメーションの待機
  async _waitAnimation(target) {
    const animations = target.getAnimations();
    if (animations.length === 0) {
      return Promise.resolve();
    } else {
      await Promise.allSettled(
        animations.map((animation) => animation.finished)
      );
    }
  }

  // アコーディオンの開閉
  async _toggle(e) {
    e.preventDefault();
    if (this.isAnimating) return;

    this.isAnimating = true;
    this.isOpen = !this.isOpen;

    if (this.isOpen) {
      this.details.open = true;
      requestAnimationFrame(() => {
        requestAnimationFrame(async () => {
          this.details.classList.add("is-open");
          await this._waitAnimation(this.panel);
          this.isAnimating = false;
        });
      });
    } else {
      this.details.classList.remove("is-open");
      await this._waitAnimation(this.panel);
      this.details.open = false;
      this.isAnimating = false;
    }
  }

  // open属性の監視(ページ内検索用)
  _observeHidden() {
    this.observer = new MutationObserver(() => {
      if (this.isAnimating) return;
      this.isOpen = this.details.open;
      this.details.classList.toggle("is-open", this.isOpen);
    });

    this.observer.observe(this.details, {
      attributes: true,
      attributeFilter: ["open"]
    });
  }
}

// クラスの実行
const elements = document.querySelectorAll(".js-accordion");

elements.forEach((el) => {
  const accordion = new Accordion(el);
  accordion.init();
});
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.