Getting started with react-spring

Posted: Edit on GitHub

As a front end developer, you will inevitably run into animations and transitions. If you're in the React ecosystem and your application has many complex animations, react-spring is a great library that helps to build your animations. In this post we will see how to get started with some simple examples.

Overview

react-spring is a spring-physics based animation library that should cover most of your UI related animation needs... The principle you will be working with is called a spring, it does not have a defined curve or a set duration. In that it differs greatly from the animation you are probably used to. We think of animation in terms of time and curves, but that in itself causes most of the struggle we face when trying to make elements on the screen move naturally, because nothing in the real world moves like that - https://react-spring.io

The engine behind react-spring works based on physics and it tries to animate your elements based on how they would if they were in the real world. If you have taken physic classes in school that covers topics like simple harmonic motion or hook's law, you may have a greater appreciation for why react-spring is so brilliant. I know I do.

In essence, animations in react-spring don't run based off time, they run based off physical properties that are configured into animations and the duration of the animation is just a result of these physical properties. If you'd like to use time based animations, react-spring has an escape hatch for you to do so. Checkout the common API section

Setup

Ensure you have node and npm or yarn installed. Next, you should have a React application setup. You can do setup a React application easily by using create react app.

npx create-react-app react-spring-tutorial

Once the process is complete, you should see a new folder called react-spring-tutorial created in your directory.

Navigate into that directory and install react-spring

cd react-spring-tutorial

npm install react-spring

Run the following command

npm start

and you should see your React application open in the browser

The react-spring APIs

There are 2 supported styles of the react-spring API, the Hooks API and the Render-props API. We would be using the Hooks API for this simple tutorial.

There are 5 hooks in react-spring currently:

  • useSpring a single spring, moves data from a -> b
  • useSprings multiple springs, for lists, where each spring moves data from a -> b
  • useTrail multiple springs with a single dataset, one spring follows or trails behind the other
  • useTransition for mount/unmount transitions (lists where items are added/removed/updated)
  • useChain to queue or chain multiple animations together

- https://www.react-spring.io/docs/hooks/basics

useSpring

The useSpring API serves as the basis for learning how to use the other 4 APIs.

We will build the following animated component below

Translate me...Weee!

Create a file in the src folder called Translate.js and insert the following code

//Translate.js
import React from 'react';
import { useSpring, animated } from 'react-spring';

const Translate = () => {
  const spring = useSpring({
    from: {
      transform: `translate(0px)`,
    },
    to: {
      transform: `translate(120px)`,
    },
    config: {
      mass: 6,
    },
  });

  return <animated.div style={{ ...spring, display: 'inline-block' }}>Translated Div</animated.div>;
};

export default Translate;

Replace the contents of App.js with the code below

// App.js
import React from 'react';
import Translate from './Translate';

function App() {
  return (
    <div>
      <Translate />
    </div>
  );
}

export default App;

If you have your server running, you should see the Translated Div text being translated across the screen on page load. If so, you've created your first animated component! 🥳

Let's breakdown the code that was just written in Translate.js

const spring = useSpring({
  from: {
    transform: `translate(0px)`,
  },
  to: {
    transform: `translate(120px)`,
  },
  config: {
    mass: 6,
  },
});

useSpring accepts an object with a from and to property. There's an optional config property which allows you to fine tune the animation settings.

from indicates the values of properties from which the animation starts. In our case, we are saying the value of the transform property shall start from translate(0px).

to indicates the values that properties should animate towards. In the example, we are animating towards translate(120px)

It's good to note that react-spring does recognize css property values such as the translate value we are using here. Other examples include animating colors, color: 'red' to color: 'blue', or with rgb color: rgb(0,0,0) to color: rgb(255,255,255)

And we do not necessarily need to animate css properties. react-spring animates values. So we could define our own keys and react-spring will animate the values accordingly. We just need to use the interpolate helper method. For example

const Translate = () => {
  const spring = useSpring({
    from: {
      myXTranslateValue: 0,
    },
    to: {
      myXTranslateValue: 120,
    },
    config: {
      mass: 6,
    },
  });

  return (
    <animated.div
      style={{
        transform: spring.myXTranslateValue.interpolate(myXTranslateValue => `translate(${myXTranslateValue}px)`),
        display: 'inline-block',
      }}
    >
      Translated Div
    </animated.div>
  );
};

The config in our example changes the mass property to 6. The default value for the mass property is 1. Increasing the mass will increase the inertia of the spring/animation. Just imagine what would happen if you have a large weight on a spring compared to a lighter weight. The heavier one will bob about longer.

For more info on what properties you can configure under config, take a look at https://www.react-spring.io/docs/hooks/api

The value returned from useSpring contains your animated values. In order to make sense of these animated values, you need to pass it to an animated component provided by react-spring.

useSprings

The useSprings API allows you to create a list of springs with different configurations

To demonstrate useSprings, let's create a horizontal bar graph shown below. Click the Animate button to see how the bar graph will animate on page load.

Note: The animation below is not staggered. The appear to be staggered because each bar has a different mass and expand at different rates. Hence it looks like they are staggered. We will do staggederd animations in the useTrail section.

In App.js, comment out <Translate/> and import and use <BarGraph/>

// App.js
import React from 'react';
import Translate from './Translate';
import BarGraph from './BarGraph';

function App() {
  return (
    <div>
      {/* <Translate /> */}
      <BarGraph />
    </div>
  );
}

export default App;

Create a new file in the src folder called BarGraph.js and insert the following code

// BarGraph.js
import React from 'react';
import { useSprings, animated } from 'react-spring';

const bars = [
  {
    key: 'bar1',
    color: 'green',
    from: {
      width: '0px',
    },
    to: {
      width: '100px',
    },
    config: {
      mass: 20,
    },
  },
  {
    key: 'bar2',
    color: 'blue',
    from: {
      width: '0px',
    },
    to: {
      width: '250px',
    },
    config: {
      mass: 30,
    },
  },
  {
    key: 'bar3',
    color: 'red',
    from: {
      width: '0px',
    },
    to: {
      width: '150px',
    },
    config: {
      mass: 13,
    },
  },
];

const BarGraph = () => {
  const springs = useSprings(
    bars.length,
    bars.map(({ color, key, ...config }) => config)
  );

  return springs.map((spring, index) => (
    <animated.div
      style={{
        ...spring,
        height: '20px',
        marginBottom: '10px',
        backgroundColor: bars[index].color,
      }}
    />
  ));
};

export default BarGraph;

Let's go through what we have in BarGraph.js

const springs = useSprings(
  number,
  [config, config, config....]
);

useSprings accepts 2 arguments. The first will be a number which indicates the number springs to generate and the 2nd argument is an array of spring configurations. You will be returned with an array of animated values which you can iterate over

Our spring configurations are listed in bars.

// Our list of spring configurations
const bars = [
  {
    key: 'bar1', // we need a key as we are generating and array of components
    color: 'green', // this is to give a static color to each bar
    from: {
      width: '0px',
    },
    to: {
      width: '100px',
    },
    config: {
      mass: 20,
    },
  },
  ...
]

and we insert bars into useSprings accordingly

const springs = useSprings(
  bars.length,
  bars.map(({ color, key, ...config }) => config) // we are omitting color and key prop as we are not animating them
);

We then iterate over springs to generate a list of horizontal bars, each with a specific animation defined in bars

return springs.map((spring, index) => (
  <animated.div
    key={bars[index].key}
    style={{
      ...spring,
      height: '20px',
      marginBottom: '10px',
      backgroundColor: bars[index].color,
    }}
  />
));

useTrail

We will build a trailing animation with a set of vertical bars. Each vertical bar will animate after the previous one with the help of useTrail. Click on the animate button below to see how it'll look like on page load.

In App.js comment out <BarGraph/> and insert <StaggeredBars/>

import React from 'react';
import Translate from './Translate';
import BarGraph from './BarGraph';
import StaggeredBars from './StaggeredBars'; //import StaggeredBars

function App() {
  return (
    <div>
      {/* <Translate /> */}
      {/* <BarGraph /> */}
      <StaggeredBars /> // Use StaggeredBars
    </div>
  );
}

export default App;

Create a new file called StaggeredBars.js and insert the following code

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

const colors = ['red', 'green', 'blue', 'orange', 'purple', 'yellow'];

const StaggeredBars = () => {
  const trailSprings = useTrail(colors.length, {
    from: { height: '0px' },
    to: { height: '80px' },
  });

  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', height: '500px' }}>
      {trailSprings.map((spring, index) => (
        <animated.div
          key={colors[index]}
          style={{
            ...spring,
            width: '20px',
            marginRight: '10px',
            transformOrigin: 'bottom',
            backgroundColor: colors[index],
          }}
        />
      ))}
    </div>
  );
};

export default StaggeredBars;

Just like the useSprings api, it accepts 2 arguments. However, the 2nd argument this time only accepts a single spring configuration.

useTransition

The useTransition api is useful when you want to animate components that are mounting/unmounting from the page. Click on the button below to see how the component will animate when we are mounting/unmounting it.

I don't think the api/documentation for useTransition is really clear about what type of arugments it accepts and what they do. However, this comment on github helps to clear things up a little: https://github.com/react-spring/react-spring/issues/606#issuecomment-476311041

Yo

In App.js comment out <StaggeredBars/> and insert <DisappearingComponent/>

import React from 'react';
import Translate from './Translate';
import BarGraph from './BarGraph';
import StaggeredBars from './StaggeredBars';
import DisappearingComponent from './DisappearingComponent';

function App() {
  return (
    <div>
      {/* <Translate /> */}
      {/* <BarGraph /> */}
      {/* <StaggeredBars /> */}
      <DisappearingComponent />
    </div>
  );
}

export default App;

Create a new file called DisappearingComponent.js and insert the following code

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

const DisappearingComponent = () => {
  const [isDisplay, setIsDisplay] = useState(true);
  const transitions = useTransition(isDisplay, null, {
    from: { transform: `translateX(50px)`, opacity: 0 },
    enter: { transform: `translateX(0px)`, opacity: 1 },
    leave: { transform: `translateX(50px)`, opacity: 0 },
  });

  return (
    <>
      <div style={{ width: '50px', height: '20px' }}>
        {transitions.map(
          ({ item, key, props }) =>
            item && (
              <animated.div key={key} style={{ ...props, display: 'inline-block' }}>
                Yo
              </animated.div>
            )
        )}
      </div>
      <button
        onClick={() => {
          setIsDisplay(prevState => !prevState);
        }}
      >
        click
      </button>
    </>
  );
};

export default DisappearingComponent;

Let's checkout what's going here.

const [isDisplay, setIsDisplay] = useState(true);
const transitions = useTransition(isDisplay, null, {
  from: { transform: `translateX(50px)`, opacity: 0 },
  enter: { transform: `translateX(0px)`, opacity: 1 },
  leave: { transform: `translateX(50px)`, opacity: 0 },
});

useTransition accepts 3 arguments. The 1st argument is a changing state and in this case, the state is a boolean value. The 2nd argument is a function that receives the 1st argument as a parameter and you are supposed to return a value to act as a key. However, in our example above, since our 1st argument is just a boolean, we can just declare a null value instead of a function. The last argument is an object describing the transition lifecycles

In our example, we are using the 3 basic life cycles from, enter, leave which would probably cover most use cases.

from describes the properties from which the animation starts. enter describes the properties that the animation transitions towards when component is mounted. leave describes the properties that the animation transitions towards before the component unmounts.

For more info on other properties that can be configured, head to https://www.react-spring.io/docs/hooks/use-transition

{
  transitions.map(
    ({ item, key, props }) =>
      item && (
        <animated.div key={key} style={{ ...props, display: 'inline-block' }}>
          Yo
        </animated.div>
      )
  );
}

useTransition returns an array of objects that consist of item, props and key

item refers to the index of the item in the 1st argument. In our example, this will be a boolean value. props are the animating values that should be passed on to the style prop of the animated component. key is a value produced by useTransition to help with supplying unique key props when rendering a list of React components.

When we click animate, we are toggling the isDisplay state and we can see the animation occur as we are conditionally rendering the animated.div

useChain

useChain allows us to set the order of declared spring animations. In our example, we will chain a useSpring together with a useTrail animation. Click animate below to see the example in action.

In App.js comment out <DisappearingComponent/> and insert <TranslateStaggeredBars/>

import React from 'react';
import Translate from './Translate';
import BarGraph from './BarGraph';
import StaggeredBars from './StaggeredBars';
import DisappearingComponent from './DisappearingComponent';
import TranslateStaggeredBars from './TranslateStaggeredBars';

function App() {
  return (
    <div>
      {/* <Translate /> */}
      {/* <BarGraph /> */}
      {/* <StaggeredBars /> */}
      {/* <DisappearingComponent /> */}
      <TranslateStaggeredBars />
    </div>
  );
}

export default App;

Create a new file called TranslateStaggeredBars.js and insert the following code:

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

const colors = ['red', 'green', 'blue', 'orange', 'purple', 'yellow'];

const TranslateStaggeredBars = () => {
  const [expanded, setExpanded] = useState(false);

  const springRef = useRef();
  const spring = useSpring({
    from: { transform: `translateX(80px)` },
    to: { transform: `translateX(0px)` },
    ref: springRef,
    config: config.stiff,
    reverse: expanded,
  });

  const trailRef = useRef();
  const trailSprings = useTrail(colors.length, {
    from: { height: '5px' },
    to: { height: '80px' },
    ref: trailRef,
    reverse: !expanded,
  });

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

  return (
    <div style={{ height: '500px' }}>
      <animated.div
        style={{
          ...spring,
          height: '100px',
          display: 'inline-flex',
          alignItems: 'flex-end',
          marginBottom: '15px',
        }}
      >
        {trailSprings.map((trailSpring, index) => (
          <animated.div
            key={colors[index]}
            style={{
              ...trailSpring,
              width: '20px',
              marginRight: '10px',
              transformOrigin: 'bottom',
              backgroundColor: colors[index],
            }}
          />
        ))}
      </animated.div>
      <div>
        <button
          type="button"
          onClick={() => {
            setExpanded(prevState => !prevState);
          }}
        >
          Click to Animate
        </button>
      </div>
    </div>
  );
};

export default TranslateStaggeredBars;

We are using the same colors array from our example in useTrail section. We also introduced ref and the reverse keys

const springRef = useRef();
const spring = useSpring({
  from: { transform: `translateX(80px)` },
  to: { transform: `translateX(0px)` },
  ref: springRef,
  config: config.stiff, // this is a preset from react-spring
  reverse: expanded,
});

const trailRef = useRef();
const trailSprings = useTrail(colors.length, {
  from: { height: '5px' },
  to: { height: '80px' },
  ref: trailRef,
  reverse: !expanded,
});

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

useChain takes an array of refs to determine the order of execution. The refs that are created (springRef and trailRef) are included in the animation configuration.

The reverse key allows us to swap the from and to properties of the animation.

Summary

All the hook api's are very similar and they build off from the simplest useSpring api.

The examples here are simple enough to get you started on animating your React components. There are more advanced techniques (such as chaining using async/await methods and using interpolation) that I will write about in another post sometime later but for now, i'll refer you to the react-spring documentation. Hope you have learned something from this post!