function animationInterval(ms, signal, callback) { // Prefer currentTime, as it'll better sync animtions queued in the // same frame, but if it isn't supported, performance.now() is fine. const start = document.timeline ? document.timeline.currentTime : performance.now(); function frame(time) { if (signal.aborted) return; callback(time); scheduleFrame(time); } function scheduleFrame(time) { const elapsed = time - start; const roundedElapsed = Math.round(elapsed / ms) * ms; const targetNext = start + roundedElapsed + ms; const delay = targetNext - performance.now(); setTimeout(() => requestAnimationFrame(frame), delay); } scheduleFrame(start); }