Vers. 1.0
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
(() => {
|
||||
// wrap content into .inner for independent inner animation
|
||||
document.querySelectorAll('.timeline .content').forEach((c) => {
|
||||
if (!c.querySelector('.inner')) {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'inner';
|
||||
wrap.innerHTML = c.innerHTML;
|
||||
c.innerHTML = '';
|
||||
c.appendChild(wrap);
|
||||
}
|
||||
});
|
||||
|
||||
// delegate clicks for toggles
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.toggle');
|
||||
if (!btn) return;
|
||||
const content = btn.nextElementSibling;
|
||||
if (!content) return;
|
||||
toggle(content, btn);
|
||||
});
|
||||
|
||||
function toggle(content, button) {
|
||||
const isOpen = content.classList.contains('open');
|
||||
|
||||
// ensure we have an inner element
|
||||
const inner = content.querySelector('.inner') || content;
|
||||
|
||||
// measure current height
|
||||
const computed = getComputedStyle(content);
|
||||
const startHeight = parseFloat(computed.height) || 0;
|
||||
|
||||
// freeze current height so transition starts from visual state
|
||||
content.style.height = startHeight + 'px';
|
||||
|
||||
if (isOpen) {
|
||||
// close: remove open class to start inner exit animation, then collapse height
|
||||
content.classList.remove('open');
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
content.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// after paint, animate height to 0
|
||||
requestAnimationFrame(() => {
|
||||
// start from the frozen pixel height
|
||||
content.style.height = startHeight + 'px';
|
||||
requestAnimationFrame(() => {
|
||||
content.style.height = '0px';
|
||||
});
|
||||
});
|
||||
|
||||
const onEnd = (ev) => {
|
||||
if (ev.propertyName !== 'height') return;
|
||||
content.style.height = '';
|
||||
content.removeEventListener('transitionend', onEnd);
|
||||
};
|
||||
content.addEventListener('transitionend', onEnd);
|
||||
|
||||
} else {
|
||||
// open: add class first so computed scrollHeight includes padding/margins
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
content.setAttribute('aria-hidden', 'false');
|
||||
|
||||
// ensure starting height (likely 0)
|
||||
content.style.height = startHeight + 'px';
|
||||
|
||||
// paint, add class, then measure target height
|
||||
requestAnimationFrame(() => {
|
||||
content.classList.add('open');
|
||||
requestAnimationFrame(() => {
|
||||
const target = content.scrollHeight;
|
||||
content.style.height = target + 'px';
|
||||
|
||||
const onEnd = (ev) => {
|
||||
if (ev.propertyName !== 'height') return;
|
||||
content.style.height = '';
|
||||
content.removeEventListener('transitionend', onEnd);
|
||||
};
|
||||
content.addEventListener('transitionend', onEnd);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user