React SVG Animation (with React Spring) #2
Tags: react, reactspring, reactanimation, svganimation
Published: 2020-12-20 18:40:40

Are you a video learner? This blog post is also available in a video form on YouTube:
{% youtube GZYy5t16ucs %}

Look at the animation below 👇

![private data related icons rotating and scaling up and down next to a woman standing](https://dev-to-uploads.s3.amazonaws.com/i/ac21250e0by4kr05m3sk.gif)

You'll learn how to create this SVG animation in React Spring and what's tricky about animating `transform: scale(...)` and `transform: rotate(...)` in SVG.

(Full source code available on [CodeSandbox](https://codesandbox.io/s/quizzical-platform-qc8gz?file=/src/App.js))

# Table of Contents
1. [How to Prepare the SVG for Animation?](#chapter-1)
2. [Why the "Naive" Approach Does NOT Work?](#chapter-2)
3. [How Does transform-origin Work in SVG?](#chapter-3)
4. [Final touches](#chapter-4)
5. [Bonus: How to do this in Framer motion and GSAP?](#chapter-5)

<a name="chapter-1"></a>
<br />
# How to Prepare the SVG for Animation?

> I assume you already know how to include SVGs in React. If not, check out [my previous post](https://dev.to/tomdohnal/react-svg-animation-with-react-spring-1-4koh) (or [YouTube video](https://youtu.be/fnzlniSEFN4)) where I explain everything from scratch.

For this animation, we're using [an SVG](https://raw.githubusercontent.com/CerebralFart/UndrawMirror/master/images/Private%20data.svg) I found on the [unDraw website](https://undraw.co/search). After you download the SVG and convert the [SVG to JSX](https://svg2jsx.com/), go ahead and locate the three icons in the SVG code and create separate React components out of them. (So that it's easier to work with.)

You should end up with something like this:

```jsx
import React from 'react'

function Icon1() {
    return <g>{/* ... */}</g>
}
function Icon2() {
    return <g>{/* ... */}</g>
}
function Icon3() {
    return <g>{/* ... */}</g>
}

function App() {
    const icons = [<Icon1 />, <Icon2 />, <Icon3 />]

    return (
        <svg /* ... */>
            {icons}
            {/* ... */}
            {/* ... */}
        </svg>
    )
}
```

Next, add a toggle button which will trigger the enter/leave animation

```jsx
import React, { useState } from 'react'

// ...
// ...

function App() {
    const [toggle, setToggle] = useState(false)

    // ...
    // ...

    return (
        <>
            <button
                type="button"
                onClick={() => {
                    setToggle(!toggle)
                }}
            >
                Toggle animation
            </button>
            {/* ... */}
            {/* ... */}
        </>
    )
}
```

You're now ready to start animating!

You can see the code for this section on CodeSandbox:
{% codesandbox part-1-d577c %}

<a name="chapter-2"></a>
<br />
# Why the "*Naive*" Approach Does NOT Work?

If you're familiar with animating HTML elements (or you've read the [previous tutorial](https://dev.to/tomdohnal/react-svg-animation-with-react-spring-1-4koh)), you might think: "Oh, this animation is kinda easy, I'm just gonna use `transform: rotate(...) scale(...)`  on the icon components and I'll be good to go".

Honestly, it seems like the perfectly reasonable thing to do. So pull up React Spring (or your favourite animation tool) and give it a try:

```jsx
// ...
import { animated, useSprings } from 'react-spring'

// ...
// ...

function App() {
    // ...
    // ...

    const springs = useSprings(
        3,
        icons.map(() => ({
            transform: toggle ? 'rotate(0deg) scale(1)' : 'rotate(360deg) scale(0)',
        })),
    )

    const animatedIcons = springs.map((style, index) => (
        <animated.g style={style}>{icons[index]}</animated.g>
    ))

    return (
        <>
            {/* ... */}
            <svg /* ... */>
                {animatedIcons} {/* `animatedIcons` instead of `icons` */}
                {/* ... */}
            </svg>
        </>
    )
}
```

The code looks quite okay in my opinion. Nonetheless, if you trigger the animation you'll be unpleasantly surprised as you'll see this:

![icons rotating around the top-left corner rather than around their own centres](https://dev-to-uploads.s3.amazonaws.com/i/8cs8p0bv94geah804mkz.gif)

While the icons *are* being animated, they all seem to be rotating around a single point rather than each being rotated individually around their own centre points.

That's weird, you think. You might even have used `transform: rotate(...)` or `transform: scale(...)` for animating HTML elements and you're sure that it worked just fine. Yes, you're right, this *would* work just fine if we were animating HTML elements. But animating SVG elements is a bit trickier...

You can see the source code for this section on Codesandbox:
{% codesandbox part-2-o443z %}

<a name="chapter-3"></a>
<br />
# How Does `transform-origin` Work in SVG?

If you look at the (somewhat broken) animation above, you'll notice that the items are rotating around a point somewhere in the top-left corner of the screen. That's not what you want, you want the icons to be rotating around their own centre points. Is there a way to do that? The thing you're looking for is called `transform-origin`

According to [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin), the `transform-origin` property "sets the origin for an element's transformations". Well, what does it really do? I think it's best demonstrated in a combination with `transform: rotate(...)` where it specifies the point which the element you're animating should revolve around. 

How come you've never needed `transform-origin` for animating HTML elements? It's because its default value is `50% 50%` (could be written as `center`) for HTML elements which corresponds to their middle points. For SVG elements, however, the default value is `0, 0` which corresponds to the top-right point in the SVG's viewbox.  

Look at animation below to see how animating `transform: rotate(...)` with **default** `transform-origin` works in SVG. 👇
![an animation of what happens when you apply the default transform-origin for SVG](https://dev-to-uploads.s3.amazonaws.com/i/ae2f1hfto7wopp6glvhr.gif)


The first idea on how to fix you might have could be this: If the default value of `transform-origin` is `0, 0` for SVGs and `50% 50%` (could be written as `center`) for HTML elements, let's manually set the `transform-origin` to `center` for the SVG and it's gonna behave how I want (rotate around the centres of the elements).

If you were to try it, however, you wouldn't like the result. That's because setting `transform-origin: center` works differently for HTML and SVG elements. For HTML elements, it sets the origin point to the *centre of the* *actual element you're animating*. For SVG element it sets the origin point to the *centre of the* *viewbox which contains your SVG element.*

Have a look at the animation below to get a visual intuition for what's going on. 👇

![an animation of what happens when you apply transform-origin: center for SVG](https://dev-to-uploads.s3.amazonaws.com/i/h7my33l126quqfxx64rn.gif)

After all the struggle it seems like there is not an easy solution for making SVG elements rotate around their own centre points. If you think that, I've got good news for you. It can be fixed by adding one extra line of CSS. Add `transform-box: fill-box` in addition to `transform-origin: center` and watch the animation behave just like you wanted!

The `transform-box` property specifies what the `transform-origin` property should relate to. Its default value is `transform-box: view-box` which makes the `transform-origin` relate to the SVG viewbox. If you set it to `transform-box: fill-box`, it's going to relate to the element it's applied to. So the centre of the rotation is going to be the centre of the element you're rotating rather than the centre of the viewbox.

Have a look at the animation below for a more visual explanation. 👇

![an animation of what happens when you apply transform-box: fill-box for SVG](https://dev-to-uploads.s3.amazonaws.com/i/qx37734q0z8885pc5225.gif)

Now that we've dived deep into the SVG transform intricacies, it's time to apply what we've learnt to our SVG animation:

```jsx
// ...
// ...

function App() {
    // ...
    // ...

    const animatedIcons = springs.map((style, index) => (
        <animated.g
            style={{
                transformOrigin: 'center', // <- make it centre
                transformBox: 'fill-box', // <- of the element
                ...style,
            }}
        >
            {icons[index]}
        </animated.g>
    ))

    // ...
    // ...
}
```

You can see the source code of this section on CodeSandbox
{% codesandbox part-3-ip21x %}

(One downside to using `transform-box: fill-box` is that it is *not* supported in legacy browsers (IE11). You could still achieve setting the `transform-origin` to the centre of the element by using exact pixel values like this: `transform-origin: 120px 160px` where `120px 160px` would be the centre of the SVG element.)

<a name="chapter-4"></a>
<br />
# Final touches

Compare the animation we've got at this point with what we'd like it to look like:

What we've got 👇

![the animation that we've programmed so far](https://dev-to-uploads.s3.amazonaws.com/i/9un3w3rlq09u2ijwndi6.gif)

What we'd like it to look like 👇

![the animation that we want to have](https://dev-to-uploads.s3.amazonaws.com/i/g3rhyd0lugs6fqk5axzu.gif)

The latter animation has got more of a playful feel to it. It boils down to two things.

1. Staggered effect (icons animating one after another)
2. Wobbly effect (adding "bounciness")

## Add staggered effect

To add the staggered effect, let's use the `delay` option passed to the `useSpring` function. (We used this approach in the [previous tutorial](https://dev.to/tomdohnal/react-svg-animation-with-react-spring-1-4koh), too.)

We leverage the fact that an `index` is passed to the `.map(...)` function and add a different delay for each of the icons:

```jsx
// ...
// ...

function App() {
    // ...
    // ...

    const springs = useSprings(
        3,
        icons.map((_, index) => ({
            // ...
            // ...
            delay: index * 50, // 1st icon -> 0ms, 2nd icon -> 50ms, 3rd icon -> 100ms
        })),
    )

    // ...
    // ...
}
```

## Add wobbly effect

Adding the wobbly effect comes down to configuring the spring animation. More specifically, decreasing the `friction`. Friction controls the spring "resistance". The higher the friction, the *less* "bouncy" animation. The lower the friction, the *more* "bounciness".

It's usually best to fine-tune the exact numbers of the animation configuration manually.

```jsx
// ...
// ...

function App() {
    // ...
    // ...

    const springs = useSprings(
        3,
        icons.map((_, index) => ({
            // ...
            // ...
            config: {
                friction: 16, // the default value is 26 (we *decrease* it to add bounciness)
            },
        })),
    )

    // ...
    // ...
}
```

You can see the code for this section on CodeSandbox:
{% codesandbox part-4-fnj5l %}

<a name="chapter-5"></a>
<br />
# Bonus: How to do this in Framer motion and GSAP?

Everyone uses a different animation library. Nonetheless, the animation principles are pretty much the same. Some libraries do more of the manual work for you (e. g. Framer Motion which applies makes the "rotate" and "scale" animations work with SVG out-of-the-box), some are more bare-bones like React spring. Some libraries are framework specific (React Spring and Framer Motion) and some are more general (GSAP where you have to orchestrate your animation using `useEffect` and `useRef`). 

If you're interested, have a look at the implementation of the same animation in React Spring, Framer Motion, and GSAP on Codesandbox 
{% codesandbox exciting-mayer-ew5wk %}