Getting started with react-spring
Published -
Last updated: 15 Nov 2023
This overview of react-spring is based on @react-spring/web@9.7.3
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
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.
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
These are the 5 hooks from react-spring that we’ll cover:
useSpring
a single spring, moves data from a -> buseSprings
multiple springs, for lists, where each spring moves data from a -> buseTrail
multiple springs with a single dataset, one spring follows or trails behind the otheruseTransition
for mount/unmount transitions (lists where items are added/removed/updated)useChain
to queue or chain multiple animations together
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
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/web";
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 to
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.to(
(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://react-spring.dev/docs/components/use-spring#reference
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/web";
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/web";
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.
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/web";
const DisappearingComponent = () => {
const [isDisplay, setIsDisplay] = useState(true);
const transitions = useTransition(isDisplay, {
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(
(style, item) =>
item && (
<animated.div style={{ ...style, 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, {
from: { transform: `translateX(50px)`, opacity: 0 },
enter: { transform: `translateX(0px)`, opacity: 1 },
leave: { transform: `translateX(50px)`, opacity: 0 },
});
useTransition
accepts 2 arguments. The 1st argument is a changing state and in this case, the state is a boolean value. The 2nd 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://react-spring.dev/docs/components/use-transition
{
transitions(
(style, item) =>
item && (
<animated.div style={{ ...style, display: "inline-block" }}>
Yo
</animated.div>
),
);
}
useTransition
returns transition function
This transition function
takes in a render function that returns a component. The render function is called with 2 arguments. The first is the style
argument which contains the current styling state of the animation. The 2nd argument refers to the data you passed in as the 1st argument to useTransition
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 } from "react";
import {
useTrail,
animated,
useSpring,
useChain,
config,
useSpringRef,
} from "@react-spring/web";
const colors = ["red", "green", "blue", "orange", "purple", "yellow"];
const TranslateStaggeredBars = () => {
const [expanded, setExpanded] = useState(false);
const springRef = useSpringRef();
const spring = useSpring({
from: { transform: `translateX(80px)` },
to: { transform: `translateX(0px)` },
ref: springRef,
config: config.stiff,
reverse: expanded,
});
const trailRef = useSpringRef();
const trailSprings = useTrail(colors.length, {
from: { height: "5px" },
to: { height: "80px" },
ref: trailRef,
reverse: !expanded,
});
useChain([springRef, trailRef]);
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 = useSpringRef();
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 = useSpringRef();
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 ref
s to determine the order of execution. The ref
s 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!