Million Miles Technologies

How to Create Interactive Animations Using React Spring — SitePoint


, , and . 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, while useSpring 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:

  1. 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)
  2. 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.

Text fading in an out with each button click

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,
  };
  
  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 and to with transitions: these can be used within enter, leave, and update 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).

Clicking add and remove buttons adds and removes elements

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.

Three vertical product circles grow from nothing

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:

  1. numberOfItems. This is a required number that specifies how many elements we want to animate in the “trail”.

  2. 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',
    },
    
  3. 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, 
    duration: 2000, 
  });
  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, 
  duration: 2000, 
});

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

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>
  );

animating a stack of cards

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]);

a row of circles expanding horizontally and vertically

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.

three horizontal lines moving to the left across the page and changing color

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.

animations

Related blogs