<!-- ※ 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>コピー&ペーストで使えるコンポーネントのサンプルです。</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 & 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();
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.