React SVG Animation (with React Spring) #1
Tom Dohnal
Posted on November 21, 2020
Are you a video learner? This blog post is also available in a video form on YouTube:
Look at the animation below 👇
You'll learn how to build it using React and react-spring
library.
Table of Contents
- How to Find an SVG to Animate?
- How to Include an SVG in React?
- How to Prepare the SVG for Animation?
- How to Animate the SVG Elements?
- How to Make the Animation Look Good?
- 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).
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>
)
}
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>
</>
)
}
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 (
<>
// ...
// ...
</>
)
}
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>
</>
)
}
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.
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,
};
})
);
(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.
Posted on November 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.