しらべものドット宇宙HTMLコーダーが必死こいて〇〇〇を目指すブログ

jsでアコーディオン

		<details class="js-details">

<summary class="js-summary">
<span class="summary_inner">
概要<span class="icon"></span>
</span>
</summary>
<div class="content js-content">
<div class="content_inner">折りたたまれている部分です。</div>
</div>
</details>
<details class="js-details">
<summary class="js-summary">
<span class="summary_inner">
概要<span class="icon"></span>
</span>
</summary>
<div class="content js-content">
<div class="content_inner">折りたたまれている部分です。</div>
</div>
</details>
<script>
'use strict';

{
document.addEventListener('DOMContentLoaded', () => {
setAccordion();
});

const setAccordion = () => {
const details = document.querySelectorAll('.js-details');
const RUNNING_VALUE = 'running';
const IS_OPENED_CLASS = 'is-opened';

details.forEach(element => {
const summary = element.querySelector('.js-summary');
const content = element.querySelector('.js-content');

summary.addEventListener('click', event => {
event.preventDefault();

if (element.dataset.animStatus === RUNNING_VALUE) {
return;
}

if (element.open) {
element.classList.toggle(IS_OPENED_CLASS);

const closingAnim = content.animate(
closingAnimKeyframes(content),
animTiming
);

element.dataset.animStatus = RUNNING_VALUE;

closingAnim.onfinish = () => {
element.removeAttribute('open');
element.dataset.animStatus = '';
};
} else {
element.setAttribute('open', 'true');

element.classList.toggle(IS_OPENED_CLASS);

const openingAnim = content.animate(
openingAnimKeyframes(content),
animTiming
);
element.dataset.animStatus = RUNNING_VALUE;

openingAnim.onfinish = () => {
element.dataset.animStatus = '';
};
}
});
});
};

const animTiming = {
duration: 400,
easing: 'ease-out',
};

// 閉じる
const closingAnimKeyframes = content => [
{
height: content.offsetHeight + 'px',
opacity: 1,
},
{
height: 0,
opacity: 0,
},
];

// 開く
const openingAnimKeyframes = content => [
{
height: 0,
opacity: 0,
},
{
height: content.offsetHeight + 'px',
opacity: 1,
},
];
}
</script>
<style>
summary {
display: block;
}

summary::-webkit-details-marker {
display: none;
}

.summary_inner {
cursor: pointer;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border: 1px solid #d2beff;
font-weight: bold;
color: #002255;
}

.icon {
display: block;
position: relative;
width: 24px;
margin-left: 6px;
flex-shrink: 0;
transform-origin: center 43%;
transition: transform 0.4s;
}

details.is-opened .icon {
transform: rotate(180deg);
}

.icon::before,
.icon::after {
content: '';
position: absolute;
display: block;
width: 15px;
height: 3px;
background-color: #7050ff;
}

.icon::before {
left: 0;
transform: rotate(45deg);
}

.icon::after {
right: 0;
transform: rotate(-45deg);
}

.content {
overflow: hidden;
background-color: #f0f2ff;
}

.content_inner {
padding: 24px 48px;
display: flex;
flex-direction: column;
gap: 16px;
}
</style>