Properly Use rAF

Last updated at
Ref:
Anatomy of a video game - MDN
DOM Performance (Reflow & Repaint)
JavaScript Animations

requestAnimationFrame is the API to replace setTimeout/setInterval when code affects the DOM, which makes the reflow/repaint of the DOM look more smoothly (not always, but better). So how do we use it?

window.requestAnimationFrame(function doSomethingJustBeforeNextFrame() {})

Er… What is frame? It simply means when you recursively calling rAF, the browser will try to schedule your callback by VSync, which is about 60 FPS (60 calls per second) on most of the computers.

Animations

We can show animations which are more complex than CSS keyframes. (e.g. draw something on the canvas, change dom elements.) Which, in my opinion, means we should play most of the animations through CSS or the element.animate() API. Let’s see it in action!

Click me to see the ripple.

Games

Our games may be made in the form of a game loop, which is discussed in this article. I will copy the result here:

const MyGame = {};
function main(frame) {
    MyGame.stopMain = window.requestAnimationFrame(main);
    update(frame);
    render(); // must be run in less than 16ms (10ms in general)
}
main(performance.now());

Er… It will become difficult if we take these into consideration:

  1. update() may be very slow
  2. render() may be very slow
  3. requestAnimationFrame() may not run at 60 FPS
  4. Game may be paused when it is put in background pages

We all know that there is no silver bullet. However, what could we do?

  1. don't call update() too often, move calculation tasks to workers
  2. use cache, offscreen canvas and other optimizations
  3. if the game is depending on real time, then don't rely on frames
  4. either design a paused state or take care of resuming states

Throttle

Since rAF can make the callback run at the rate of VSync. We could wrap it as a throttle helper:

function throttle(fn) {
    let timer
    return (...args) => {
        if (!timer) {
            timer = requestAnimationFrame(() => {
                timer = null
                fn.apply(this, args)
            })
        }
    }
}

Just note that the callback may not be called if the page is in background.

Problems

rAF does not solve any of the reflow/repaint performance problem. In fact, it is more like a throttle. You still have to take care of performances. — cache computed attributes, batch changes to the DOM, etc.

Luckly, we will have OffscreenCanvas in the future (for now, all chromium based browsers have support it). Through which we could move some drawing work to workers and let main UI thread respond to user input.