Creating Complex Animations in React Using react-spring

Joshua Hall

Before, we went over how to create single animations with react-spring. Now we’ll be looking over the different methods for combining multiple springs for more complicated animations.

Prerequisites

Knowing how to create basic animations with useSpring is necessary, which was covered here. Besides that, we’ll also be using a few React hooks, which you can brush up on here.

Installation

We’re just going to need react-spring and we can get started using create-react-app:

$ npx create-react-app react-spring-example
$ npm i react-spring 

Transitions

One problem with useSpring is that it doesn’t give us any control over the lifecycle of our component, we could change the opacity and position to make it ‘disappear’ but it would still get rendered to our DOM. Instead we can use useTransition to control the animations as it is mounted and unmounted from the view.

I personally don’t like the syntax for this, so I’ll try to break it down a bit.

We set a variable to useTransition, it needs our items, their keys, then an object with the from, enter, and leave properties.

Our item can either be an array of what we want it applied to or a boolean, in this case our on state. If it’s an array then we need to use an inline arrow function to return a property off of each item we want to be the key, like item => item.key. If it’s just a boolean then we can just set the key to false.

To apply our transition we need to map what’s returned from useTransition and destructure what we passed-in earlier. Instead of passing the transition into our style, we need to destructure our item (our on state), the keys, and the props (our animation).

App.js

import React, { useState } from 'react';
import { animated, useTransition } from 'react-spring';

const [on, toggle] = useState(false);

const transition = useTransition(on, null, {
  from: { opacity: 0 },
  enter: { opacity: 1 },
  leave: { opacity: 0 }
});

return (
<div>
  {transition.map(({ item, key, props }) => (
  item && <animated.div style={props} >Hello world</animated.div>
  ))}

  <button onClick={() => toggle(!on)}>Change</button>
</div>
);

If you look at our h1 in the inspector you’ll notice that it is actually being added and removed from the DOM every click, instead of just being hidden. This can have some performance benefits for components that aren’t needed most of the time, like a modal or mobile navbar.

Springs

Instead of manually creating another spring whenever we want a slightly different animation, we can use the useSprings hook to generate many versions from an array.

It works kind of like a mix between useSpring and useTransition in that it takes an array, maps over it, and uses the from and to properties to assign the animation. For our styles we can just pass in the values from each item in our array.

Once we have our springs returned we can map over them again to render them to the screen.

App.js

import React, { useState } from 'react';
import { animated, useSprings } from 'react-spring';


const App = () => {
  const [on, toggle] = useState(false);

  const items = [
    {
      color: 'red',
      opacity: .5
    }, {
      color: 'blue',
      opacity: 1
    }, {
      color: 'green',
      opacity: .2
    }, {
      color: 'orange',
      opacity: .8
    },
  ];

  const springs = useSprings(items.length, items.map(item => ({
    from: { color: '#fff', opacity: 0 },
    to: {
      color: on ? item.color : '#fff',
      opacity: on ? item.opacity : 0
    }
  })));

  return (
    <div>
      {springs.map(animation => (
        <animated.div style={animation}>Hello World</animated.div>
      ))}

      <button onClick={() => toggle(!on)}>Change</button>
    </div>
  )
};

Trails

useTrail lets us create an effect similar to both useSpring and useSprings, it will allow us to attach an animation to multiple items but instead of being executed at the same time, they will be executed one after the other. It just takes a number for how many we want and the style object.

App.js

import { animated, useTrail, config } from 'react-spring';

const App = () => {
  const [on, toggle] = useState(false);

  const springs = useTrail(5, {
    to: { opacity: on ? 1 : 0 },
    config: { tension: 250 }
  });

  return (
    <div>
      {springs.map((animation, index) => (
        <animated.div style={animation} key={index}>Hello World</animated.div>
      ))}

      <button onClick={() => toggle(!on)}>Change</button>
    </div>
  )
};

Chains

We have two options when using multiple animations on the same elements, we can either have them run at the same time or one after the other. To give them a specific order we can use useChain to link them together.

We’re going to need useRef from React to do this. For all of our animations we just need to create a ref variable and pass it to our animations ref property. Notice that now your animation stops working when you do this.

useChain takes an array with our references in the order we want to be executed. We can use another ternary operator if we want the order to change on enter or leave. To implement them we can just destructure them into our style.

App.js

import React, { useState, useRef } from 'react';
import { animated, useSpring, useTrail, useChain} from 'react-spring';


const App = () => {
  const [on, toggle] = useState(false);

  const springRef = useRef();
  const spring = useSpring({
    ref: springRef,
    from: { opacity: .5 },
    to: { opacity: on ? 1 : .5 },
    config: { tension: 250 }
  });

  const trailRef = useRef();
  const trail = useTrail(5, {
    ref: trailRef,
    from: { fontSize: '10px' },
    to: { fontSize: on ? '45px' : '10px' }
  });

  useChain(on ? [springRef, trailRef] : [trailRef, springRef]);

  return (
    <div>
      {trail.map((animation, index) => (
        <animated.h1 style={{ ...animation, ...spring }} key={index}>Hello World</animated.h1>
      ))}

      <button onClick={() => toggle(!on)}>Change</button>
    </div>
  );
};

Conclusion

On their own, react-spring’s hooks may not be too extraordinary, but when you begin to combine them in interesting ways you can create incredibly precise and flexible animations.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#