Skip to content

Instantly share code, notes, and snippets.

@jakearchibald
Last active July 11, 2024 23:52
Show Gist options
  • Save jakearchibald/cb03f15670817001b1157e62a076fe95 to your computer and use it in GitHub Desktop.
Save jakearchibald/cb03f15670817001b1157e62a076fe95 to your computer and use it in GitHub Desktop.
export 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);
}
// Usage
import { animationInterval } from './1.js';
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And to stop it:
controller.abort();
@zizifn
Copy link

zizifn commented Jan 1, 2022

so if I just want the counter for second level precision, I just need set setInterval delay less than 1000ms, should be working fine?

    const start = Date.now();
    setInterval(() => {
        const gaps = (Date.now() - start);
        const seconds = Math.floor(gaps / 1000);
        updateUI(seconds)
    }, 900); // change less than 1000ms

@Sepush
Copy link

Sepush commented Jan 1, 2022

yeah finally I did it like this

@stevengrimaldo
Copy link

Is this gist something i can use to create a countdown timer with? like to a specific date. Or would this not be for something like that?

@jakearchibald
Copy link
Author

@zizifn no, that still drifts. That's why the code in this gist is more complicated.

@jakearchibald
Copy link
Author

@stevengrimaldo

Is this gist something i can use to create a countdown timer with?

Yes

@toFrankie
Copy link

toFrankie commented May 25, 2024

The video is very interesting and I benefited a lot from it. 👍

Now the time obtained each time requestAnimationFrame(frame) is executed is accurate, but due to the use of setTimeout, the main thread will still be blocked and the number of seconds will jump.

Suppose there is a time-consuming synchronization task:

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)
}


let count = 0
function longRunningTask() {
  console.time('long task')
  for (let i = 0; i < 1500000000; i++) {
    count += 1
  }
  console.timeEnd('long task')
}

const controller = new AbortController()

// 👇
setTimeout(() => longRunningTask(), 5000)

// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
  console.log('tick!', time)
})

// And to stop it:
// controller.abort()

The actual performance is as follows:

tick! 1161.4
tick! 2144.9
tick! 3161.7
tick! 4161.3
tick! 5161.4
long task: 8215.17578125 ms
tick! 13381.1
tick! 14148.2
tick! 15148.4
tick! 16148.6
tick! 17148.8
...

Assuming there is a time-consuming task, it seems that the only way is to put this time-consuming task into a Worker or adopt a time-slicing solution to divide it into several small tasks so that the main thread has time to update the UI, right?

@jakearchibald

@jakearchibald
Copy link
Author

If your thread is blocked, then both setTimeout and requestAnimationFrame callbacks will be delayed. Avoid running long tasks on the UI thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment