Thursday, October 2, 2014

Scaling usage requestAnimationFrame and a solution for avoiding style recalculation more than once per frame.

Scaling usage requestAnimationFrame and a solution for avoiding style recalculation more than once per frame.

Recently some code that I wrote for Google internally was open sourced as part of the Closure library: "goog.dom.animationFrame"
https://github.com/google/closure-library/blob/master/closure/goog/dom/animationframe/animationframe.js

The library is quite interesting as it solves a big issue when working with browser animations in larger teams:

2 or more tasks want to do work in the same animation frame.

Say task 1 is animating your navigation while task 2 is animating some parts of the content.

In the worst case now the DOM operations look like this:

Animation frame:
Task 1 measures position of object A.
 - Triggers style recalculation
Task 2 changes position of object A.
 - Invalidates previous style calculation.
Task 1 measure position of object B.
 - Triggers another style recalculation
Task 2 changes position of object B.

The new library solves this with the following API:

var animationTask = goog.dom.animationFrame.createTask({
  measure: function(state) {
    state.width = goog.style.getSize(elem).width;
  },
  mutate: function(state) {
    goog.style.setWidth(elem, Math.floor(state.width / 2));
    animationTask();
  }
});

When code wants to schedule an animation frame, they have to supply two functions: One to measure the DOM and one to mutate it.

Because of this separation the library can serialize all the measure and mutate operations to happen in 2 phases: First all the measurements and then all the mutations.

Thus the operations against the DOM per animation frame now look like this:

Animation frame:
Task 1 measures position of object A.
 - Triggers style recalculation
Task 1 measure position of object B.
Task 2 changes position of object A.
Task 2 changes position of object B.

Note, how one style recalculation disappeared – and as more tasks are added this scales further without introducing any additional recalculations.

Besides this major feature, the library also solves the following problems:
- designed to minimize per frame memory allocation.
- designed to avoid accidental scheduling of the same work more then once per animation frame.

If this sounds useful, go ahead wrap the code in some module system of your choice and make your app a little silkier.
https://github.com/google/closure-library/blob/master/closure/goog/dom/animationframe/animationframe.js