presentations / animations /shared /sequence-helpers.js
evalstate's picture
evalstate HF Staff
Keep sequence animations visible between cycles (avoid disappearing/blank states)
f24a186 verified
function runSequenceAnimation({
selectors,
cyclePause = 2000,
fadeOutOffset = 4000,
initialDelay = 500,
lastDelayOverride,
onShow,
scrollContainerSelector,
useFadeOut = false,
} = {}) {
const groups = (selectors || []).flatMap((selector) =>
Array.from(document.querySelectorAll(selector))
);
if (groups.length === 0) {
return;
}
const delays = groups.map((el) => parseInt(el.dataset.delay, 10) || 0);
const maxDelay = Math.max(...delays, 0);
const finalDelay = Math.max(maxDelay, lastDelayOverride || 0);
const scrollContainer = scrollContainerSelector
? document.querySelector(scrollContainerSelector)
: null;
let timers = [];
let runId = 0;
function clearTimers() {
timers.forEach((id) => clearTimeout(id));
timers = [];
}
function schedule(fn, delay) {
const id = setTimeout(fn, delay);
timers.push(id);
return id;
}
function runAnimation(activeRunId) {
if (activeRunId !== runId) {
return;
}
clearTimers();
groups.forEach((el) => el.classList.remove('show', 'fade-out'));
if (scrollContainer) {
scrollContainer.scrollTop = 0;
}
// Force reflow to ensure class removal is processed before re-adding
void document.body.offsetHeight;
groups.forEach((el) => {
const delay = parseInt(el.dataset.delay, 10) || 0;
schedule(() => {
if (activeRunId !== runId) {
return;
}
el.classList.add('show');
if (onShow) {
onShow(el);
}
if (scrollContainer) {
scrollContainer.scrollTop = scrollContainer.scrollHeight;
}
}, delay);
});
let restartDelay = finalDelay + cyclePause;
if (useFadeOut) {
const fadeOutTime = finalDelay + fadeOutOffset;
schedule(() => {
if (activeRunId !== runId) {
return;
}
groups.forEach((el) => el.classList.add('fade-out'));
}, fadeOutTime);
restartDelay = fadeOutTime + 1000 + cyclePause;
}
schedule(() => runAnimation(activeRunId), restartDelay);
}
function start() {
runId++;
clearTimers();
schedule(() => runAnimation(runId), initialDelay);
}
if (document.readyState === 'complete') {
start();
} else {
window.addEventListener('load', start, { once: true });
}
// Restart only on bfcache restores to avoid duplicate starts.
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
start();
}
});
}