Next we'll look at the JavaScript that does the work. The meat of this technique is in the play()
function, which is called when the user clicks on the "Run" button.
function play() {
document.querySelector(".box").className = "box";
requestAnimationFrame((time) => {
requestAnimationFrame((time) => {
document.querySelector(".box").className = "box changing";
});
});
}
This looks weird, doesn't it? That's because the only way to play an animation again is to remove the animation effect, let the document recompute styles so that it knows you've done that, then add the animation effect back to the element. To make that happen, we have to be creative.
Here's what happens when the play()
function gets called:
- The box's list of CSS classes is reset to
"box"
. This has the effect of removing any other classes currently applied to the box, including the "changing"
class that handles animation. In other words, we're removing the animation effect from the box. However, changes to the class list don't take effect until the style recomputation is complete and a refresh has occurred to reflect the change. - To be sure that the styles are recalculated, we use
window.requestAnimationFrame()
, specifying a callback. Our callback gets executed just before the next repaint of the document. The problem for us is that because it's before the repaint, the style recomputation hasn't actually happened yet! - Our callback cleverly calls
requestAnimationFrame()
a second time! This time, the callback is run before the next repaint, which is after the style recomputation has occurred. This callback adds the "changing"
class back onto the box, so that the repaint will start the animation once again.
Of course, we also need to add an event handler to our "Run" button so it'll actually do something:
document.querySelector(".runButton").addEventListener("click", play, false);