Smooth Game Animations in React

Jen Liu
Tech @ Quizlet
Published in
6 min readFeb 18, 2016

We built a new game on Quizlet called Gravity to help students learn vocabulary while having fun. As words fall down your screen from outer space in the form of “asteroids,” you race to type the answer before they hit the planet.

Building all the animations we needed in React gave us an opportunity to push the boundaries of what it can do.

React is still relatively new, and it wasn’t created to support animations — its declarative nature and under-the-hood DOM updating system do not provide us with time-based control over our UI. However, we felt that the benefits of using React outweighed the limitations [1], and we were willing to place a bet that the awesome community around React would help us fill in the gaps — and they did.

Different animations in the game had different requirements, and were supported by different approaches: for asteroids falling we used react-tween-state, for planet advancement VelocityJS, and for the level-up badge, CSS keyframes. This post is about how and why we built each animation the way we did.

Animation I: Asteroids falling downwards

The Need:

Animate a single dimension of an element, with the ability to control timing and start & stop the animation at will.

The Solution:

React-tween-state: a simple library that updates a given property of an element over the course of a period of time by updating the state. In the author’s words: “The equivalent of React’s this.setState, but for animated tweens: this.tweenState.”

We declared the asteroid’s fall via three values: a start value, end value, and duration. We could also pause the fall on demand by grabbing the asteroid’s current position and setting that to be the new start and end values. This API gave us full control over the asteroid’s position over time while also making it simple to animate continuously without user input.

Alternatives:

React-motion is a more advanced library that allows you to animate components using spring physics. While our linear-timed animation may not accurately represent the physics of an asteroid falling through space, it is well-suited for a student trying to learn a vocabulary word (imagine how stressful the game would be if the asteroids accelerated!) Although react-motion is newer and more extensible, react-tween-state is smaller and provides all the functionality we need, so we chose the simpler library.

Animation II: A planet zooming in from afar

The Need:

Animate the position and size of 3 images simultaneously, between two game states. On any screen size, the bottom planet should be centered while the next two planets are in the top right corner.

Planet size is based on viewport size

The Solution:

Velocity-react: a React wrapper for the Velocity.JS DOM animation library, which provides the power of CSS animations in JavaScript.

Since viewport dimensions are only accessible via Javascript, it was natural to calculate other dimensions in Javascript as well. React’s inline styles made it easy to apply these styles and Velocity allowed us to transform them in a performant way. Incidentally, the APIs for inline styles and Velocity animations are similar (just pass in key-value pairs of CSS properties!) so this made the code pretty nice and readable, too.

Alternatives:

Since there were no user interactions involved with this animation, we could have also implemented it entirely in CSS, using transform and translate. Without information about the viewport dimensions from CSS, however, we could only size and position the planets using percentages. In that case, we would have to translate the planets using percentages as well. However, when you use percentages with CSS’s translate, the percentages are applied to the element being translated rather than the parent — this just would have involved a bit of extra math to get the desired effect. We tried applying CSS transitions to the top and left properties but that turned out to have terrible performance compared to CSS transform.

Animation III: A badge zooming/fading in and out

The Need:

Animate scale and opacity of an image in the middle of another animation.

The Solution:

CSS3 keyframes. Since the badge is always the same size and we always want it to zoom in and out after a fixed delay, CSS animations were enough to get the job done. We attached an is-levelingUp class during the level-up state and added a 500ms delay to give the planets a head start.

Alternatives:

We tried using ReactCSSTransitionGroup to animate the badge in and out of the DOM:

The outcome of this was inconsistent — the animation was sometimes fast-forwarded or cut off. Fine-grained timing was not in our control. Turns out CSSTransitionGroup is good for transitioning in or out of the DOM, but not in and out of the DOM. We then realized there was no need to actually remove the badge from the DOM — we could just render it with 0 opacity when we didn’t want to show it.

Another option was using VelocityJS, which allows you to specify the timing of multiple components in parallel with its UI Pack plugins. If we had used Velocity, all the timing would be in one place and we wouldn’t run the risk of CSS and Javascript animations interfering with each other or being timed incorrectly. The downside is that we would break modularity by having the component’s animation controlled by another component, perhaps some central animation coordinator. Ultimately, we decided it was okay if the badge and planet synchronization had a small margin of error and when we tested it across browsers, it actually appeared to be fairly consistent. Coordinating the animation of multiple components via Velocity is something worth exploring further, though.

Conclusion

There is currently no single, clear-cut way to implement animations in React; rather there are many ways to animate components depending on your needs. As React and these supporting libraries continue to develop, we’ve found that experimenting with different options (and remembering to test across browsers!) is the best way to find something that works. In the future, perhaps animations in React will become more streamlined. In the meantime, we are thankful to the developer community for creating all these solutions thus far and excited to see what further developments arise down the road.

In what ways have you animated components in React? Do you have suggestions for other ways to implement the animations I described? Tweet @jenkliu. And if building animations to make learning more fun for millions of people is something that interests you, let me know — we’re hiring!

Check out the animations yourself while brushing up on U.S. State Capitals and play Gravity here.

[1] We’ve enjoyed using React on previous projects with its declarative nature, modularity, performance, and overall collaboration-friendliness.

Discuss on Hacker News: https://news.ycombinator.com/item?id=11127922

--

--