<section class="accordion js-accordion" aria-labelledby="heading1">
<h3 id="heading1" class="heading">
<a class="trigger" role="button" href="#panel1" aria-expanded="false" aria-controls="panel1">
EVOLABとは
<span class="icon" role="img" aria-hidden="true"></span>
</a>
</h3>
<div id="panel1" class="panel" hidden="until-found">
<div class="panel-inner">
<p>EVOWORXのエンジニアによるWeb制作会社向けの情報ポータルです。</p>
</div>
</div>
</section>
<section class="accordion js-accordion" aria-labelledby="heading2">
<h3 id="heading2" class="heading">
<a class="trigger" role="button" href="#panel2" aria-expanded="false" aria-controls="panel2">
BLOG
<span class="icon" role="img" aria-hidden="true"></span>
</a>
</h3>
<div id="panel2" class="panel" hidden="until-found">
<div class="panel-inner">
<p>EVOWORXのエンジニアによるブログをお届けします。</p>
</div>
</div>
</section>
<section class="accordion js-accordion" aria-labelledby="heading3">
<h3 id="heading3" class="heading">
<a class="trigger" role="button" href="#panel3" aria-expanded="false" aria-controls="panel3">
SAMPLE
<span class="icon" role="img" aria-hidden="true"></span>
</a>
</h3>
<div id="panel3" class="panel" hidden="until-found">
<div class="panel-inner">
<p>コピー&ペーストで使えるコンポーネントのサンプルです。</p>
</div>
</div>
</section>
<section class="accordion js-accordion" aria-labelledby="heading4">
<h3 id="heading4" class="heading">
<a class="trigger" role="button" href="#panel4" aria-expanded="false" aria-controls="panel4">
LAB
<span class="icon" role="img" aria-hidden="true"></span>
</a>
</h3>
<div id="panel4" class="panel" hidden="until-found">
<div class="panel-inner">
<p>アニメーションなどのコードの実験室です。</p>
</div>
</div>
</section>
<section class="accordion js-accordion" aria-labelledby="heading5">
<h3 id="heading5" class="heading">
<a class="trigger" role="button" href="#panel5" aria-expanded="false" aria-controls="panel5">
GUIDELINE & TIPS
<span class="icon" role="img" aria-hidden="true"></span>
</a>
</h3>
<div id="panel5" class="panel" hidden="until-found">
<div class="panel-inner">
<p>弊社のコーディングガイドラインやWeb制作のお役立ち情報です。</p>
</div>
</div>
</section>
.accordion {
background-color: #fff;
border-radius: 10px;
padding: 20px;
&:not(:first-of-type) {
margin-top: 30px;
}
.trigger {
width: 100%;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
column-gap: 12px;
color: #333;
font-weight: 700;
text-decoration: none;
&::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;
}
&[aria-expanded="true"] {
.icon {
&::before {
rotate: 180deg;
}
}
}
.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;
transition: height 0.4s ease;
.panel-inner {
padding-top: 32px;
padding-bottom: 4px;
}
}
}
body {
background-color: #333;
padding: 30px;
}
View Compiled
class Accordion {
constructor(el) {
// DOM要素
this.el = el;
if (!this.el) return;
this.trigger = null;
this.panel = null;
// インスタンス
this.observer = null;
// フラグ
this.isOpen = null;
this.isAnimating = false;
}
// 初期化
init() {
this.trigger = this.el.querySelector('[role="button"][aria-expanded]');
this.panel = this.el.querySelector(
`#${this.trigger.getAttribute("aria-controls")}`
);
this.isOpen = !this.panel.hidden;
this.panel.style.height = this.isOpen ? "" : "0";
this._observeHidden();
this.trigger.addEventListener("click", this._toggle.bind(this));
this.trigger.addEventListener("keydown", (e) => {
e.key === " " && this._toggle();
});
}
// 破棄
destroy() {
this.isAnimating = false;
if (this.trigger) {
this.trigger.removeEventListener("click", this._toggle.bind(this));
this.trigger.removeEventListener("keydown", 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;
this.trigger.ariaExpanded = this.isOpen;
if (this.isOpen) {
this.panel.hidden = false;
this.panel.style.height = `${this.panel.scrollHeight}px`;
await this._waitAnimation(this.panel);
this.panel.style.height = "auto";
this.isAnimating = false;
} else {
this.panel.style.height = `${this.panel.scrollHeight}px`;
requestAnimationFrame(() => {
requestAnimationFrame(async () => {
this.panel.style.height = "0";
await this._waitAnimation(this.panel);
this.panel.hidden = "until-found";
this.isAnimating = false;
});
});
}
}
// hidden属性の監視(ページ内検索用)
_observeHidden() {
this.observer = new MutationObserver(() => {
if (this.isAnimating) return;
this.isOpen = !this.panel.hidden;
this.trigger.ariaExpanded = this.isOpen;
this.panel.style.height = this.isOpen ? "auto" : "0";
});
this.observer.observe(this.panel, {
attributes: true,
attributeFilter: ["hidden"]
});
}
}
// クラスの実行
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.