Animation 60FPS
Last updated
Last updated
The RxFx principles of “return the work” and “fake it till you make it” are shown in this example of using Javascript rather than CSS animation.
Concepts: Stubbing handlers, requestAnimationFrame, unrolling recursion. .
In this example of a carnival game, a player on the left and a player on the right can press (or tap) the targets to advance their unicorn across the screen. We'll build this using RxFx, but first let's answer why we'd choose to animate with JS, not just CSS properties.
In CSS-based animation, a change of the left
or transform
style property is accompanied with a transition
directive like transition: left 0.25s;
. This suffers the age-old issue of 'events coming in too fast'—Javascript has no idea that an animation is going on. It thinks that a change of the left
property is instantaneous. So a player could just mash that target 10 times quickly and the unicorn would be all the way across the board. The gameplay could be improved if animation could finish before the unicorn moves again. This is just RxFx' listenBlocking
strategy, so let's begin!
Our setup: We're in React, and we have an event handler to trigger events of type player1/touch/start
to the bus. And we have a state setter for the X position of player1. A portion of this might be:
The movement listener will start out very rough, but we will enhance it until we have full 60fps motion. We'll start with a crude 2-step animation in listenBlocking
mode, to throttle the player's movement and make it more interesting:
The first, familiar argument to the listener is the event we listen for. The final argument is an Observer saying what we do each time the handler produces an event, which is calling our state setter.
But why is the handler in the middle returning only two 'frames' of motion? Solely to illustrate the "fake it till you make it" principle. With RxFx we can substitute one movement process for another with no change to the surrounding architecture! So until we have smooth animation figured out, 2 steps of animation will do. We have our gameplay right—we just need to improve the display.
To upgrade to animationFrames, the smoothest kind of animation, used to require that we jump through hoops to write some recursive function calls. But RxJS has a better idea built in: an Observable that hides that recursion, and gives us frames we can observe, just as we observed our 2-frame animation! In our 2-frame animation we knew the amount of time between each frame, but in general with animation frames we do not. The details of how we do this are in the reduceToDeltas
function (not shown) but what we see is how we map each time delta
to an amount to move by in the X direction:
Basically: if we have 10 frames, we move about 1/10th of the distance each frame! Now, getting smooth animation is just a matter of swapping this Observable of X deltas for our original stub!
Boom! We have the gameplay we want, and all the smoothness the browser can achieve. We can eventually get even better performance by applying the X position to the DOM through a transform2d
or similar. But that too would not change our architecture - the animationFrames
Observable would automatically adjust and just serve shorter deltas!
What we have shown is how to stub one process with another ("fake it till you make it"), how to do animation outside of any library or framework, and a way to replace recursive code with evented code.
See the full game here: https://codesandbox.io/s/animation-recipe-yd7nlr, and try it with some kids on a mobile device if you get a chance - in my user-testing it is well received!
*Note: Some details have been left out of this article, and it is no substitute for learning the true principles of animation.