In this article, we’ll explore React Spring, a JavaScript-based animation library. We’ll look at its features, including the various hooks and components it offers, and how to make use of them in React applications.
The concept of animation in React applications has been an ever-evolving aspect of frontend development. Initially, animations in React were achieved using CSS transitions and animations, but as React Applications become more complex, the need for more powerful animation tools became apparent. This led to the development of JavaScript-based animation libraries, such as Framer Motion, Remotion, and React Spring. Each of these libraries offers unique features and capabilities for creating animations in React.
Background Knowledge
This article assumes you have the following:
- a basic understanding of React and React Hooks
- familiarity with JavaScript and JSX syntax
- Node.js and
npm
(oryarn
) installed on your development environment - a code editor such as Visual Studio Code
Introduction to React Spring
React Spring is a JavaScript animation library used to create interactive animations in React applications. Unlike traditional CSS-based animations or other React animation libraries, React Spring uses physics-based animations, which mimic real-world motion and create a more natural-looking effect.
These animations can be applied to any property of React components, including position, scale, opacity, and more. This makes it a powerful tool for developers looking to enhance the user experience of their React applications with captivating animations.
Setting up React Spring in our project
To animate components in our React project using React Spring, we have to follow these steps:
-
Download and install the React Spring library. We can do this either using
npm
oryarn
:npm install react-spring
yarn add react-spring
These commands will install the
react-spring
library and its dependencies into our project directory. -
After installing React Spring, we need to import the necessary components and hooks into our React components to start animating elements. This can be achieved using the following syntax:
import { animated, (hook) } from 'react-spring'
In the code snippet above, we’re importing two dependencies (hook and animated) from the React Spring library. Here’s a breakdown of how each of them works and why they have to be imported.
Animated
In React Spring, the animated
namespace provides a set of components that are used to animate elements in our React application. It provides the animated versions of standard HTML elements such as <div>
, <span>
, and <img>
. These animated elements can be used in place of their normal HTML elements, allowing us to apply animations to them using React Spring’s animation hooks.
Hooks
React Spring provides several hooks that help to create animations in React components. These hooks simplify the process of managing animations and make it easy to integrate them into our components. Here are some of the main hooks provided by React Spring:
-
useSpring. This is usually used in most cases as it creates a single spring animation that changes data from the initial state to another.
-
useTransition. This animates the addition, removal, or reordering of list items. It manages the animation lifecycle of elements as they enter or leave the DOM, allowing for smooth transitions between different states of a list.
-
useTrail. This is used to create multiple spring animations that create a “trail” effect, where each spring follows or trails behind the previous one.
-
useChain. Just like a chain this is used to define a sequence of animations using by specifying the order in which they should occur.
-
useSprings. Although this is similar to
useSpring
,useSprings
is used for managing multiple spring animations at the same time, whileuseSpring
manages a single spring animation.
To further understand how these work, let’s look at the different animation styles we can achieve with each of these hooks.
Using useSpring to Create Animations
The useSpring
hook in React Spring is used to create animations using spring physics. It allows us to define the start and end points of an animation and uses its library to handle the transition between them. For example:
const props = useSpring({
opacity: 1,
from: { opacity: 0 }
});
In this example, we’ve created a function that changes the opacity of an element from 0 to 1. This function can be called on various elements depending on our animation effects. Let’s look at the steps to take when using the useSpring
hook to create animations …
First, import the dependencies needed for the animation:
import { useSpring, animated } from "react-spring";
Next, we need to define a component and use the useSpring
hook to create animated values. The useSpring
hook accepts two primary arguments:
-
Configuration object. This defines the properties of our animation, including:
- from: the initial state of the animated value (such as opacity: 0)
- to: the target state of the animated value (such as opacity: 1)
- config (optional): an object to fine-tune the spring physics behavior (such as mass, tension, friction)
-
Callback function (optional). We can use a function to create a dynamic configuration based on props or data.
Creating a useSpring
animation can be achieved using two different methods: using an object literal, and using a function parameter.
Using an object literal
We can define an object with the properties we want to animate, such as opacity
or color
, and pass it to the useSpring
hook. This approach allows us to specify the target values for the animation directly.
To explain how this works, let’s create a simple component that animates the opacity of an element:
import React, { useState } from 'react';
import { useSpring, animated } from 'react-spring';
function App() {
const [isVisible, setIsVisible] = useState(false);
const opacityAnimation = useSpring({
opacity: isVisible ? 1 : 0,
config: {
tension: 200,
friction: 20
}
});
const toggleVisibility = () => setIsVisible(!isVisible);
return (
<div>
<button onClick={toggleVisibility} aria-label={isVisible ? 'Hide' : 'Show'}>
{isVisible ? 'Hide' : 'Show'}
</button>
<animated.div style={opacityAnimation}>
This text will fade in and out with spring physics.
</animated.div>
</div>
);
}
export default App;
In this code snippet, we create a button that toggles the visibility of some text when clicked. It does this by using two hooks, useState
and useSpring
.
It uses useState
to check if the text is visible or not and creates an animation that changes the opacity of a text based on the condition:
opacity: isVisible ? 1 : 0
This gives an animation effect once the button that calls the toggleVisibility()
function is clicked.
Using a function parameter
Alternatively, we can pass a function to the useSpring
hook. This function receives the previous animated values and returns an object with the updated values for the animation. This gives us more control over how the animation behaves over time:
const opacityConfig = {
tension: 300,
friction: 40,
};
// Define opacity animation with useSpring hook
const opacityAnimation = useSpring(() => ({
opacity: isVisible ? 1 : 0,
config: opacityConfig,
}));
In this approach, the configuration (tension and friction) is extracted into a separate object — opacityConfig
— and this offers greater flexibility for dynamic control based on state or props.
Animating List Items with useTransition
UseTransition
is a React Spring hook that animates elements in arrays as they’re added or removed from the DOM. It’s particularly useful for creating fluid animations in lists or modals. To do this, it accepts a list of possible configurations:
from
defines the initial styles for the items entering the DOM.enter
specifies the styles to animate to when items are added. We can create multi-step animations by providing an array of objects.leave
sets the styles applied when items are removed from the DOM.update
controls how to animate changes between existing items.key
allows us to explicitly define a unique key for each item. This makes it possible to define specific animations for individual items.from
andto
with transitions: these can be used withinenter
,leave
, andupdate
for more complex animations with starting and ending states defined independently.
To illustrate how useTransition
works, let’s create a component that adds and removes items from an array:
import React, { useState } from "react";
import { useTransition, animated } from "react-spring";
function App() {
const [items, setItems] = useState([]);
const addItem = () => {
const newItem = `Item ${items.length + 1}`;
setItems([...items, newItem]);
};
const removeItem = () => {
if (items.length === 0) return;
const newItems = items.slice(0, -1);
setItems(newItems);
};
const transitions = useTransition(items, {
from: { opacity: 0, transform: "translate3d(0, -40px, 0)" },
enter: { opacity: 1, transform: "translate3d(0, 0, 0)" },
leave: { opacity: 0, transform: "translate3d(0, -40px, 0)" },
});
return (
<div className="transitionDiv">
<div>
<button onClick={addItem}>Add Item</button>
<button onClick={removeItem}>Remove Item</button>
</div>
<div className="transitionItem">
{transitions((style, item) => (
<animated.div style={style} className ='list'>{item}</animated.div>
))}
</div>
</div>
);
}
export default App;
In this example, we have an App
component that manages a list of items. It provides buttons to dynamically add or remove items from the list. When the Add Item button is clicked, a new item is added to the array, and when the Remove Item button is clicked, the last item is removed from the array.
The useTransition
hook is used to manage the transitions of items in the array. Whenever the array changes (due to adding or removing items), useTransition
handles the animations for those changes according to the specified configuration (defined by the from
, enter
, and leave
properties).
Animating arrays without changes
If there are no dynamic changes in the array itself, such as adding or removing elements, useTransition
can still be used to animate each element in the array. For example:
import { useTransition, animated } from "@react-spring/web";
import "./App.css";
const name = "Product1";
const name1 = "Product2";
const name2 = "Product3";
function App({ data = [name, name1, name2] }) {
const transitions = useTransition(data, {
from: { scale: 0 },
enter: { scale: 1 },
leave: { scale: 0.5 },
config: { duration: 2500 },
});
return transitions((style, item) => (
<div className="nameBody">
<animated.div style={style} className="nameDiv">
{item}
</animated.div>
</div>
));
}
export default App;
In this example, the App
component renders a list of items and applies animations each time the page loads.
Creating Sequential Animations with useTrail
The useTrail
animation is used to create a series of animated transitions for a group or list of UI elements.
Unlike traditional animation methods that animate elements individually, useTrail
allows us to animate elements one after another, thereby creating a “trail” effect. This is usually used when creating dynamic lists, image galleries, page transitions, or any scenario where elements need to animate sequentially.
Here’s the basic structure of the syntax:
const trail = useTrail(numberOfItems, config, [trailOptions]);
Let’s break this down:
-
numberOfItems
. This is a required number that specifies how many elements we want to animate in the “trail”. -
config
. This is an object that defines the animation properties for each element in the trail. Each key in the object represents an animation property and its value can be based on our intended animation. For example:from: { opacity: 0, transform: 'translateX(50%)' }, to: { opacity: 1, transform: 'translateX(0)' }, transition: { duration: 500, easing: 'easeInOutCubic', },
-
trailOptions
(optional). This is an array of additional options for the trail. Some common options are:trailKey
: a function to generate unique keys for each element in the trail (useful for React reconciliation).reset
: a function to reset all animations in the trail.
Let’s take a look at how it works:
import React, { useState, useEffect } from "react";
import { useTrail, animated } from "react-spring";
function App() {
const [items, setItems] = useState([
{ id: 1, content: "This is a div illustrating a trail animation" },
{ id: 2, content: "This is a div illustrating a trail animation" },
{ id: 4, content: "This is a div illustrating a trail animation" },
{ id: 5, content: "This is a div illustrating a trail animation" },
]);
[]);
const trail = useTrail(items.length, {
from: { opacity: 1, transform: "translateY(0px)" },
to: { opacity: 0, transform: "translateY(100px)" },
delay: 400, // Add a delay between animations
duration: 2000, // Customize the animation duration
});
return (
<div className="container">
{trail.map((props, index) => (
<animated.div key={items[index].id} style={props} className="item">
{items[index].content}
</animated.div>
))}
</div>
);
}
export default App;
In the code snippet above, we create a CardCarousel
component that utilizes the useTrail
hook to create a trail of animations for each card carousel based on the length of the items in the array.
Note: to learn more about the useEffect
hook, check out Understanding React useEffect.
const trail = useTrail(items.length, {
from: { opacity: 1, transform: "translateY(0px)" },
to: { opacity: 0, transform: "translateY(100px)" },
delay: 400, // Add a delay between animations
duration: 2000, // Customize the animation duration
});
Here, it defines the initial and final states of the animation (from and to) as well as the transition configuration (duration and easing) which affects the way the animation is shown.
Rendering each card
To render each card, the component returns a <div>
with the class card-carousel
and maps over the trail array to render each animated card. Each card is then wrapped in an animated.div
component applying the animated styles (opacity and transform) defined in the useTrail
hook:
return (
<div className="container">
{trail.map((props, index) => (
<animated.div key={items[index].id} style={props} className="item">
{items[index].content}
</animated.div>
))}
</div>
);
Mastering Animation Sequences with useChain
Unlike standalone animations, useChain
is used to link multiple animations together, and sets a sequence on how pre-defined animations are carried out. This is particularly useful when creating dynamic user interfaces where elements need to animate one after another.
Let’s look at the syntax.
useChain
accepts an array of animation refs and an optional configuration object. Each animation ref represents a separate animation, and they’re executed in the order they appear in the array. We can also specify delays for each animation to control the timing of the sequence using this syntax:
useChain([ref1, ref2, ref3], { delay: 200 });
To illustrate how this works, let’s create a component that applies two animations on different elements and controls the animations using useChain
:
import "./App.css";
import React, { useRef } from "react";
import {
useTransition,
useSpring,
useChain,
animated,
useSpringRef,
} from "react-spring";
const data = ["", "", "", ""];
function App() {
const springRef = useSpringRef();
const springs = useSpring({
ref: springRef,
from: { size: "20%" },
to: { size: "100%" },
config: { duration: 2500 },
});
const transRef = useSpringRef();
const transitions = useTransition(data, {
ref: transRef,
from: { scale: 0, backgroundColor: "pink" },
enter: { scale: 1, backgroundColor: "plum" },
leave: { scale: 0, color: "pink" },
config: { duration: 3500 },
});
useChain([springRef, transRef]);
return (
<animated.div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "400px",
width: springs.size,
background: "white",
}}
>
{transitions((style, item) => (
<animated.div
style={{
width: "200px",
height: "200px",
display: "flex",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
marginLeft: "50px",
color: "white",
fontSize: "35px",
borderRadius: "360px",
...style,
}}
className="products"
>
{item}
</animated.div>
))}
</animated.div>
);
}
export default App;
In the code above, we’re creating two different animations, using useString
and useTransition
, and using the useChain
to manage the different animations:
useChain([springRef, transRef]);
Creating Multiple Animations Using the useSprings Hook
As we mentioned earlier, useSprings
is used to create multiple spring animations at the same time, and each of these animations has its configurations. This allows us to animate multiple elements or properties independently within the same component. For example:
import { useSprings, animated } from "@react-spring/web";
function App() {
const [springs, api] = useSprings(
3,
() => ({
from: { scale: 0, color: "blue" },
to: { scale: 1, color: "red" },
config: { duration: 2500 },
}),
[]
);
return (
<div>
{springs.map((props) => (
<animated.div style={props} className="springsText">
_______
</animated.div>
))}
</div>
);
}
export default App;
In this example, useSprings
manages an array of spring animations, each representing the animation for one item in the items array. Each item in the list is associated with a spring configuration that defines the initial and target values for the color and scale properties. React Spring then animates each item based on its corresponding configuration.
Conclusion
React Spring is a powerful animation library that enables us to create stunning and interactive animations in our React applications. As we’ve seen, these animations can be applied on various elements in our projects.
By leveraging the features of React Spring, we can achieve smoother transitions with more natural-looking effects, and greater control over our animations.
Yemi is a software developer and technical writer. She enjoys explaining technical concepts related to programming and software in understandable terms. You can read more of her blog posts at dev.to/hyemiie.