What’s the secret to fast, complex animations when building a React application? I’ll give you a hint… the solution actually avoids React, but we can utilize Redux.

The Goal

We want to animate a simple circle to the location of our mouse wherever it goes. You can play around with the full working version here:

I included both redux and non-redux solutions, as well as a button to toggle between animating top / left properties and transform properties so you can see the difference in the performance panel of Chrome dev tools.

Our Packages

To get things going, we’re going to need to install the following:

React

React DOM

Redux (or not)

GSAP (the golden goose of animation libraries)

So basically just run npm install react react-dom redux gsap and you should have everything you need.

The Basic Setup

Let’s create a file called AnimatedComponent.js for our, you guessed it, animated component.

This is mostly pretty standard, but there are a few things I want to point out here:

We are listening to move movements globally through the window object for the purpose of this example, but you can just as easily attach the listener to some element. We are using React hooks because they’re awesome. This is the same as adding the event listener on componentDidMount and removing it on componentWillUnmount , but a whole lot shorter. Don’t worry about the setTargetPosition function. For now let's assume it exists and magically sets our target position for our circle in some global state. We’ll flesh it out later.

Our Golden Rules for Responsible React Animations

So… get ready… we’re going to use state outside of React. I know I know… it’s voodoo and frowned upon. So in order to do this effectively, we need to impose some rules:

We only use state outside React if we will be updating it many times per second. If your animations are simple (ex. CSS Transitions with an active / inactive state), just use React state and no animation library. There is no need to introduce overhead and complexity. If you are doing JavaScript animations, don’t write it yourself. Use a library such as GSAP and let it do its thing outside of React. Libraries like GSAP are heavily optimized by dudes a lot smarter than you and I, and it will make your life 1000x easier. Plus it will cut down on the external state management.

Please note that this example is simple enough to be done with CSS transitions (without GSAP), but it doesn’t satisfy the active / inactive state behavior requirement, so we will use global state. Additionally, I wanted to introduce to ya’ll the power of GSAP and this is a good excuse.

Updating Our Circle’s Target Position

Okay, so let’s think about our situation. We will be updating the target position every time the mouse moves, which will be multiple times a second (rule 1). That’s a whole lot of state updates, so we’re probably going to need to deal with this outside of React.

I’ll give an example for my redux users out there, and then a short one for those that aren’t using Redux.

Redux: Our Actions & Reducers

We need an action to update the position of the circle we are animating when we move the mouse. Here is our actions.js file:

Great, that was easy. We are dispatching against the store directly here, but as Mark Erikson pointed out in the comments (thanks again!), it’s not necessary. You can pass the action creator to your connect function, for example, export default connect(null, {setTargetPosition})(AnimatedComponent) in AnimatedComponent.js . I think dispatching against the store directly creates a nice visual distinction between your components connected to Redux and components like these that aren’t affected by state changes, but it’s up to you. Now for our reducer:

Tada! We now have external state.

The Non-Redux Solution

We can also set up an extremely simple global state object with some helper functions in state.js

I won’t go through implementing the actual example with custom state, but you can see the CodeSandbox for the full example. It should be pretty clear where to swap the redux stuff for the functions above.

Back to Our Animated Component

Not too bad! Let’s get back to AnimatedComponent.js and make some updates there.

Let’s walk through every new line. First we created a ref using the useRef hook so we can point to our div:

const compRef = useRef(null);

Then we actually attach it to our div:

<div style={animatedCompStyle} ref={compRef} />

We also need to listen for state changes. The typical React Redux way is to use mapStateToProps , but in our case we don't want this to fire component updates at the React level. Luckily, we can make use of the store.subscribe() function to accomplish this.

store.subscribe(() => {

const state = store.getState();

});

This is not best practice because this will fire when any part of the state changes. I suggest using libraries such as redux-subscribe that are optimized to only listen for changes in particular slices of state. For our example, this will do just fine.

Now we can get into GSAP!

What is GSAP anyways?

GSAP (Greensock Animation Library) is the golden goose of JavaScript animation libraries. It is fast as hell, easy to use, and extremely powerful. I’ll make a separate article on this library alone, but for now just check out the docs here to see how capable it is.

We are going to leverage it to smoothly ease our div to our mouse position. Here is the code for that:

// TweenLite at a minimum takes an element, duration, and properties // to animate as input TweenLite.to(compRef.current, 1, { x: state.x, y: state.y });

TweenLite.to() is a function that fires off an animation with specified properties. Here is what's happening:

We pass a reference to our div element that we’re animating We pass the duration of our animation in seconds we specify the properties we want to animate to, in our case, x and y being the target mouse position we stored in state We can also pass different easing functions (GSAP has a ton), delays, chain multiple tweens, pass callbacks to specific points in a timeline, and much more. This is outside the scope of the tutorial though.

And that’s it! We now have fast animations using React and Redux (and a special thanks to GSAP). We managed to keep our state separate from the rest of our app, yet still utilized the same frameworks and tools we are use to leveraging.

Important Note on Animated Properties

While we can animate any property we want, we don’t want to. The difference between the following examples is massive in terms of performance.

But why? The answer lies in how the browser updates the screen. There is a multi-step process that the browser goes through when rendering a web page, the last (and fastest) step being the compositing step. This deals with the ordering of page elements after content has already been painted to the screen.

Avoid Top and Left CSS Properties

When we animate using x and y, we don’t trigger any layout changes because we’re using the transform property, which isn’t affected by layout. This means when the browser renders an update, it won’t need to paint the element again because it isn’t worried about layout changes.

When we animate using top and left, this is relative to other elements on the page. The browser is forced to re-paint the animated element on every update, which is what causes janky animations.

The conclusion is to only use translate, rotate, and scale whenever possible.

Performance Test

I have included a button in the example to swap between using x / y and top / left. You can go into the chrome dev tools, click into the performance panel, click the record button, and see how the animations using top and left fire composite AND paint events unlike the animations using transforms:

Notice Paint and Composite Layers triggering when using top + left

Conclusion

I want to mention that there are still further performance improvements that can be made here (debouncing the mouse move event calls, using redux-subscribe to only listen to updates of specific properties, etc). This should give you a good foundation to doing some cool animations in React without your performance suffering.

In the CodeSandbox I include a non-redux example as well, so if you aren’t using Redux please do check it out!