React SVG Animation (with React Spring) #1

tomdohnal

Tom Dohnal

Posted on November 21, 2020

React SVG Animation (with React Spring) #1

Are you a video learner? This blog post is also available in a video form on YouTube:

Look at the animation below 👇

animation we are going to build

You'll learn how to build it using React and react-spring library.

Table of Contents

  1. How to Find an SVG to Animate?
  2. How to Include an SVG in React?
  3. How to Prepare the SVG for Animation?
  4. How to Animate the SVG Elements?
  5. How to Make the Animation Look Good?
  6. How to Create More Advanced Animations?



How to Find an SVG to Animate?

The first step you should take to create an animated SVG is to pick an SVG image you want to animate. If you're a skilled illustrator, you can create your own. But if you're like me, websites like undraw.co will do the trick. However, the SVG we're animating is no longer there at the time of writing this article but you can still find it here.



How to Include an SVG in React?

There are two ways of including an SVG in React code.

The first one is using <img src="kitten.svg" alt="kitten" /> just like with .jpg or .png images. However, this approach is not suitable for animations as you lose control over the individual parts of the SVG you want to animate.

The second approach is using "inline SVG". This means putting the SVG code right into React (JSX)!

To do that, view the source code of this image, and copy it. Then, head over to https://svg2jsx.com and paste the copied SVG code into the editor on the left. This tool will convert the SVG to JSX. (It will close every unclosed SVG elements and convert attributes such as stop-color to stopColor)

Now copy the code from the editor on the right-hand side and paste it into your editor. The SVG will appear in your browser!

You can view the code for this section in the embedded CodeSandbox below. 👇

(I decreased the width and height of the original svg so that it fits in the CodeSandbox embed)



How to Prepare the SVG for Animation?

To be able to animate specific parts of the SVG image, you should extract them to individual React components so that it's a bit easier to work with. (Remember, you can treat the SVGs just like HTML and split it into many components.)

Because we want to animate the envelopes let's create a component for each envelope. Now comes the hard—finding out which of almost 300 lines of SVG code create the envelopes.

Open the browser devtools and try to locate the envelope using the element picker. If you locate a part of the SVG which you think is a part of the envelope, you can try deleting it (using the delete key) and see if the envelope (or its part) actually gets removed. (Use ctrl + z or cmd + z to bring it back).

devtool with located SVG

After playing with the devtools for a bit, you can identify that each envelope is composed of two path elements followed by a g element.

Now, you can head over to your editor, create <Envolope1 />, <Envolope2 />, <Envolope3>, and <Envelope4 />. You should use a g element as the parent element in your Envelope components to group the items in it. (Think of the g element as an SVG alternative to the div element.)

The last steps in this part is to create an array of the Envelope component on top of your main component and include it to where the envelopes where in the svg:

function Icon() {
  const envelopes = [
        <Envelope1 key="envelope1" />,
        <Envelope2 key="envelope1" />,
        <Envelope3 key="envelope1" />,
        <Envelope4 key="envelope1" />,
    ]

    return (
        <svg>
            // ...
            // ...
            {envelopes}
        </svg>
    )
}
Enter fullscreen mode Exit fullscreen mode

The image itself should look exactly the same as at the end of the previous section.

You can view the code for this section in the embedded CodeSandbox below. 👇



How to Animate the SVG Elements?

Finally, here comes the fun part! 🎉

First, you need something to trigger the animation. Let's use a simple button which will toggle a variable. Based on the value of that variable, the envelopes will arrive or go away.

import React, { useState } from 'react'

function Icon() {
    const [toggle, setToggle] = useState(false)
  // ...
  // ...

    return (
        <>
            <button type="button" onClick={() => { setToggle(!toggle) }}>
                Toggle animation
            </button>
            <svg>
                // ...
                // ...
            </svg>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now that you have a toggle variable to trigger the animation you can start animating.

For animating an array of elements with react-spring you should use the useSprings hook. (Read the documentation to learn more about it.)

To create the animation effect the transform: translate3d(...) and opacity are used. The value passed to the translate3d(...) function or opacity is going to change based on the toggle variable.

The useSprings hook will return an array of animated values which we can map over.

import { useSprings } from 'react-spring'

function Icon() {
    // ...
    // ...

    const envelopes = [
    // ...
    // ...
  ];

    const springs = useSpring(
      envelopes.length, // the number of springs to create
        envelopes.map(() => {
            return {
                transform: toggle
                    // toggle === true -> initial position
          ? `translate3d(0px, 0px, 0px)`
                    // toggle === false -> move the envelopes to the left
          : `translate3d(-400px, 0px, 0px)`,
                opacity: toggle ?
                    // toggle === true -> full opacity
                    1
                    // toggle === false -> full transparency
                    : 0
            }
        })
  )
  // ...
  // ...

    return (
        <>
            // ...
            // ...
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

After creating the springs you are now ready to map over them! The springs variable is an array of so-called "animated values" which you pass to the style property of elements wrapped in animated.g from react-spring.

Let's see the code excerpt with comments!

import { useSprings } from 'react-spring'

function Icon() {
    // ...
    // ...

    const envelopes = [
    // ...
    // ...
  ];

    const springs = useSpring(
      // ...
      // ...
  )

    const animatedEnvelopes = springs.map((animatedStyle, index) => (
    // use `animated.g` instead of `g` as the `animatedStyle`
    // is a special react-spring flavour of the `style` property
    <animated.g
      key={index} // YOLO
      style={animatedStyle} // apply the animated style
    >
      {envelopes[index]}
    </animated.g>
  ));

    return (
        <>
            // ...
            // ...
            <svg>
                // ...
            // ...
                {animatedEnvelopes} // use `animatedEnvelopes` instead of `envelopes`
                // ...
                // ...
            </svg>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

That wraps up this section! You now have the envelopes moving as you press the "Toggle animation" button.

You can find all the code for this section in the CodeSandbox



How to Make the animation look good?

You can probably see that the animation we created doesn't really look so good.

There are at least 3 problems:

Problem 1: The Trajectory Is Too Short

The animation trajectory seems to be a bit too short. That's because you can't see the envelopes when they go past the viewBox of the svg.

Fortunately, the fix is quite easy. You just need to manually adjust the viewBox.

(The viewBox specifies what points are visible in the svg element. It's used like this: viewBox="x0 y0 width height" where x0 specifies the left-most point, y0 specifies the top-most point and width and height specify the width and height. 🙃)

We need to give the svg some extra space on the left, therefore adjust the viewBox from 0 0 733.51 728.97 to -200 0 933.51 728.97. (Change the first number to create some space on the left and the third number to not crop the right-hand part of the image).

Problem 2: Envelopes Don't Animate in a Natural Direction

Even though the envelopes are slightly rotated we only animate them in one direction (left-right). It would seem more natural to animate in two directions (left-right + top-bottom).

How to find out how much vertical movement we should add? You could either fine-tune the translate property manually or use a bit of trigonometry. Let's do the latter. 😉

First, let's find out what angle the envelopes are rotated at. You can use the devtools to find that.

rotation angle rotated in devtools

So the rotation angle is around 83deg. And the horizontal part of the animation is 400px. We get the vertical part of the animation as: vertical = 400px * cos(83deg) which yields around 49px.

So change the translate3d(-400px, 0px, 0px) to translate3d(-400px, -49px, 0px) and you'll be good to go.

Problem 3: Envelopes don't animate individually

Envelopes animate as a group which doesn't really look natural. Creating a staggered effect would make the animation seem much more natural.

Fortunately, it's two lines of code in react-spring as we can specify delay for each animated value.

const springs = useSprings(
    envelopes.length,
    envelopes.map((_, i) => {
      return {
        // ...
        // ...
                // as we map over the envelopes, increase the delay
                // first envelope -> delay: 0ms
                // second envelope -> delay: 100ms
                // etc.
        delay: i * 100,
      };
    })
  );
Enter fullscreen mode Exit fullscreen mode

(It's worth noting that you could also use the useTrail hook instead of useSpring with delay to create a staggered effect.)

Yay! 😲The animation now looks much better! You can find the source code in the CodeSandbox below:



How to Create More Advanced Animations?

This is just the beginning—animating the using the translate3d(...) property is quite similar in SVG and HTML. However, things get more tricky if you try to animate scale, rotate properties.

If you're interested, you can follow me here or/and check out the YouTube channel where I'll be posting the tutorials.

💖 💪 🙅 🚩
tomdohnal
Tom Dohnal

Posted on November 21, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related