React SVG Animation (with React Spring) #3
Tags: react, svg, animation, reactspring
Published: 2021-03-01 20:21:05

In the third edition of the [React SVG Animation series](https://dev.to/tomdohnal/series/10252), we're going to create this 👇
!["privacy" shield animation](https://dev-to-uploads.s3.amazonaws.com/i/yd7j9odsnexqdyfte3sr.gif)

(You can find a video version of this article on YouTube! 📺)

{% youtube hQ2VJGPYsV0 %}

We're going to implement it by animating lines in SVG (the `path` element) and we'll learn how to extract animation logic into re-usable custom hooks.

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

# Table of Contents
1. [How to prepare the SVG for the animation?](#chapter-1)
2. [How we're going to build the animation?](#chapter-2)
3. [How to animate lines in SVG?](#chapter-3)
4. [How to create a re-usable animation hook?](#chapter-4)
5. [Final touches](#chapter-5)

<a name="chapter-1"></a>
<br />
# How to prepare the SVG for the animation?
Before we start talking about the animation, we need to have something to animate.

After creating a new React app using your favourite tool (e. g. `create-react-app`) and installing `react-spring@next` using your favourite package manager, copy and paste this SVG. 👇
> Note that we're using the `next` version of the `react-spring` library as the newest version (v9) is still in the `rc` stage.
```javascript
function Image() {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="286"
      height="334"
      fill="none"
      viewBox="0 0 286 334"
    >
      <path
        fill="#A78BFA"
        stroke="#A78BFA"
        strokeWidth="2"
        d="M 143, 333 C 31.09 261.823 1 73.61 1 73.61 L 143 1 v 332 z"
      />
      <path
        fill="#8B5CF6"
        stroke="#8B5CF6"
        strokeWidth="2"
        d="M 143, 333 C 254.911 261.823 285 73.61 285 73.61 L 143 1 v 332 z"
      />
      <path
        stroke="#4ADE80"
        strokeWidth="24"
        d="M75 153.5l68.081 77.5L235 97"
      />
    </svg>
  );
}
```

You can see that the SVG is comprised of three `path` elements which correspond to the two left and right part of the "shield" and the checkmark.

Let's extract them into separate components so that it's easier for us to work with them independently.

First, grab the *last* `path` element and create a `Checkmark` Component:
```javascript
function Checkmark() {
  return (
    <path
      stroke="#4ADE80"
      strokeWidth="24"
      d="M75 153.5l68.081 77.5L235 97"
    />
  );
}
```

Next, we'd like to extract the left and right part of the shield. As the animation is identical for both parts of the shield, it's a good idea to create a `ShieldPart` component which will accept a `color` and a `d` (`path` definition) as props. We'll then pass the corresponding colour and `path` definition to the `ShieldPart` components.
```javascript
function ShieldPart({ color, d }) {
  return (
    <path
      fill={color}
      stroke={color}
      strokeWidth="2"
      d={d}
    />
  );
}
```

Once you've created those components, put the inside the `svg` instead of the `path` elements.
```javascript
<svg
  // ...
>
  {/* Left shield part */}
  <ShieldPart
    d="M 143, 333 C 31.09 261.823 1 73.61 1 73.61 L 143 1 v 332 z"
    color="#A78BFA"
  />
  {/* Right shield part */}
  <ShieldPart
    d="M 143, 333 C 254.911 261.823 285 73.61 285 73.61 L 143 1 v 332 z"
    color="#8B5CF6"
  />
  <Checkmark />
</svg>
```

We're now good to go and can start talking about the animation itself.

(You can see the source code for this section on [CodeSandbox](https://codesandbox.io/s/react-svg-animation-3-1-9q576?file=/src/App.js))

<a name="chapter-2"></a>
<br />
# How we're going to build the animation?
Let's have a proper look at the animations we're going to build. 👀👇
!["privacy" shield animation](https://dev-to-uploads.s3.amazonaws.com/i/yd7j9odsnexqdyfte3sr.gif)

If you look really carefully, you can see that the animation consists of three parts.

First, the edges of the shield animate:
![edges of the shield animating](https://dev-to-uploads.s3.amazonaws.com/i/1f20lay1kxy76dbpbtbl.gif)

Then, the shield gets filled with colour:
![shield fill colour animating](https://dev-to-uploads.s3.amazonaws.com/i/ahtj2790n3s9t0fw5wiu.gif)

Lastly, the checkmark animates:
![shield checkmark animating](https://dev-to-uploads.s3.amazonaws.com/i/68p7r362ira3anhjex2r.gif)

Animating the shield "background" colour is quite straightforwards–we're justing going to animate the `fill` property (an SVG equivalent of `background` property) from `#fff` (white) to the desired colour.

However, how do we go about animating the shield edges and checkmark? Well, we need a bit of "SVG trickery" to do that. Let's learn out it in the next section.

<a name="chapter-3"></a>
<br />
# How to animates lines in SVG?
What do we even mean by "lines" in SVG? We do **not** mean the `line` element but a `path` element with a `stroke`.

Let's use our "checkmark" path element as an example.
```javascript
<path
  stroke="#4ADE80" // defines the colour of the "line"
  strokeWidth="24" // defines the width of the "line"
  d="M75 153.5l68.081 77.5L235 97"
/>
```

Strokes in SVGs are similar to borders in HTML. The `stroke` property defines the colour of the "line" (roughly equivalent to `border-color` in HTML) and `stroke-width` defines the "thickness" of the "line" (roughly equivalent to `border-width` in HTML). 

"What the heck does `stroke` and `stroke-width` have to do with animating the SVG," you might think. And you're right (partially 😉). We're going to animate neither of those properties but they *do* need to be present on the `path` for the animation to make sense. If the path would only have the `fill` property (something like `background` in HTML) and *not* `stroke`, we wouldn't be able to animate it.

Now that we've learnt about the *prerequisites* for the animation, let's move on and learn about another two properties (and these *will* actually be directly involved in the animation)–`stroke-dasharray` and `stroke-dashoffset`.

The `stroke-dasharray` property is used to turn your "solid" line into a "dashed" line and defines how wide one "dash" is.

See the demonstration below. 👇
![changing dash width](https://dev-to-uploads.s3.amazonaws.com/i/dwppf722w1u2dj9bulsn.gif)
> You can notice that if we set the range to its max value (which is equal to the length of the checkmark), the one "dash" covers the whole checkmark. This will be the key to the animation!

The `stroke-dashoffset` property defines how much "shifted" the "dashes" are. 

Have a look. 👀👇
![changing dash offset](https://dev-to-uploads.s3.amazonaws.com/i/geuf9cg6zx3t94ts91sh.gif)

You might have noticed that if you set the `stroke-dasharray` property equal to the length of the path (which you can get using `.getTotalLength()`), it appears as if there were no `stroke-dasharray` set at all. 

But is it really the case? Well, it certainly *appears* so, but it doesn't mean that it's the case. Actually, the line *is* still dashed, but the gap in the dashes is not visible as it's "after" the end of the checkmark. 

What if we, though, combined `stroke-dasharray` set to the length of the path with `stroke-dashoffset`? What would it look like? 🤔 Let's have a look:
![checkmark animating as we're changing stroke dashoffset](https://dev-to-uploads.s3.amazonaws.com/i/10t40upd1mcyy9f7xapn.gif)

What?! How's that possible? It looks like what we've wanted to achieve! The checkmark is animating!

As the `stroke-dashoffset` changes from 0 to the length of the checkmark, the checkmark is disappearing. That's because the "gap" (which's length is also equal to the length of the checkmark) gets "before" the "dash". If the `stroke-dashoffset` is set to 0, only the "dash" part is visible. If it's set to the length of the checkmark, only the "gap" part is visible.

Therefore, to animate the checkmark, you have to:
1) Set its `stroke-dasharray` to its length (you can get it by `.getTotalLength()`
2) Animate its `stroke-offset` from the length (obtained by `.getTotalLength()`) to 0.

Let's do that in the next section!

# Animating `path` in React Spring
First, we need to find out the length of the path. You can either call the `.getTotalLength()` function on the `path` element and hard-code the value, or you can use `useState` from React, and set the length of the path by passing a callback to the `ref` property:
```javascript
function Checkmark() {
  const [length, setLength] = useState(null);

  return (
    <path
      ref={(ref) => {
        // The ref is `null` on component unmount
        if (ref) {
          setLength(ref.getTotalLength());
        }
      }}
      // ...
    />
  );
}
```

Next, we'll make the `Checkmark` accept a `toggle` property which will trigger the animation.

We'll also set its `stroke-dasharray` equal to the `length` that we keep track of.

Finally, we're going to animate the `stroke-dashoffset`. We'll use the `useSpring` hook for that. If the toggle is truthy, we'll set its value to `0` (the checkmark will appear). If it's falsy, we'll set it to the value of `length` (the total length of the checkmark) and it'll disappear.
```javascript
function Checkmark({ toggle }) {
  const [length, setLength] = useState(null);
  const animatedStyle = useSpring({
    // we do *not* animating this property, we just set it up
    strokeDasharray: length,
    strokeDashoffset: toggle ? 0 : length
  });

  return (
    <animated.path
      style={animatedStyle}
      ref={(ref) => {
        // The ref is `null` on component unmount
        if (ref) {
          setLength(ref.getTotalLength());
        }
      }}
      // ...
    />
  );
}
```
> If you're not familiar with using `useSpring`, `animated`, and React Spring in general, [check out my previous posts about animating SVGs using React!](https://dev.to/tomdohnal/react-svg-animation-with-react-spring-1-4koh)

Finally, we need to pass the `toggle` variable from our main `Image` component down to the `Checkmark` component.
We'll set it to `false` initially and use the `useEffect` hook together with `setImmediate` to set it to `true` once the component mounts and the checkmark length is measured (using the `.getTotalLength()`).

```javascript
function Image() {
  const [toggle, setToggle] = useState(false);

  useEffect(() => {
    // `setImmediate` is roughly equal to `setTimeout(() => { ... }, 0)
    // Using `setToggle` without `setImmediate` breaks the animation
    // as we first need to allow for the measurement of the `path`
    // lengths using `.getTotalLength()`
    setImmediate(() => {
      setToggle(true);
    });
  }, []);

  return (
    <svg
      // ...
    >
      {/* ... */}
      <Checkmark toggle={toggle} />
    </svg>
  );
}
```

(You can find the full source-code for this section on [Codesandbox](https://codesandbox.io/s/react-svg-animation-3-2-2ev1i))

<a name="chapter-4"></a>
<br />
# How to create a re-usable animation hook?
Thus far, we've only applied what we've learnt to the checkmark animation. However, a very similar animation could be applied to animate the edges of the shield.

That's why it might be a good idea to extract the logic of animating a "line" in SVG into a separate hook.

The hook is going to be responsible for measuring the path length and animating the path based on the `toggle` variable.

So it's going to accept `toggle` as an argument and return a `style` variable (for the animation) and a `ref` variable (for the path length measurement).

```javascript
function useAnimatedPath({ toggle }) {
  const [length, setLength] = useState(null);
  const animatedStyle = useSpring({
    strokeDashoffset: toggle ? 0 : length,
    strokeDasharray: length
  });

  return {
    style: animatedStyle,
    ref: (ref) => {
      // The ref is `null` on component unmount
      if (ref) {
        setLength(ref.getTotalLength());
      }
    }
  };
}
```

We're the going the use this hook in the `Checkmark` component:

```javascript
function Checkmark({ toggle }) {
  const animationProps = useAnimatedPath({ toggle });

  return (
    <animated.path
      {...animationProps}
      // ...
    />
  );
}
```

If you now refresh the page, the animation should look exactly the same as before this refactor.

Next, let's use the very same `useAnimatedPath` hook for animating the edge of the shield in the `ShieldPart` component.

```javascript
// do *not* forget to make the `ShieldPart`
// component accept the `toggle` prop
function ShieldPart({ color, d, toggle }) {
  const animationProps = useAnimatedPath({ toggle });

  return (
    <animated.path // `path` -> `animated.path`
      {...animationProps}
      // ...
    />
  );
}
```

Finally, pass the `toggle` prop onto the `ShieldPart` components:

```javascript
function Image() {
  // ...

  return (
    <svg {/* ... */}>
      {/* Left shield part */}
      <ShieldPart
        toggle={toggle}
        // ...
      />
      {/* Right shield part */}
      <ShieldPart
        toggle={toggle}
        // ...
      />
      {/* ... */}
    </svg>
  );
}
```

If you now refresh the page, you won't really be satisfied as you'll barely see the shield edges being animated.

That's because we're not animating the `fill` (something like `background` in HTML) of the shield and the colour of the shield edges match the colour of the shield background. Let's do it and finish the animation in the next section.

(You can find the full source code of the section on [CodeSandbox](https://codesandbox.io/s/react-svg-animation-3-3-y2475?file=/src/App.js))


<a name="chapter-5"></a>
<br />
# Final touches
First, let's tackle animating the `fill` (something like `background` in HTML) of the `ShieldPart` component.

We'll use a `useSpring` hook for the animation and will animate from `#000` (white colour) when the `toggle` is falsy to the `color` property that the `ShieldPart` component accepts when the `toggle` property is truthy.

```javascript
function ShieldPart({ color, d, toggle }) {
  // rename: `animationProps` -> `animationStrokeProps`
  const animationStrokeProps = // ...
  const animationFillStyle = useSpring({
    fill: toggle ? color : "#fff"
  });

  return (
    <animated.path
      {...animationStrokeProps}
      // as the `animationStrokeProps` have a `style` property 
      // on it, it would be overriden by just passing
      // `style={animationFillStyle}`
      style={{
        ...animationStrokeProps.style,
        ...animationFillStyle
      }}
      // *remove* the `fill={color}`
      // ...
    />
  );
}
```

If you now refresh the page, the animation will look better. Just a little bit better, though. That's because everything is animating all at once. Instead, we want to animate the edges of the shield first, then fill the shield with colour and only then animate the checkmark. 

In order to do that, let's leverage the `delay` property which we can pass to the `useSpring` function.

First, let's make our custom `useAnimatedPath` accept a `delay` as an argument:
```javascript
function useAnimatedPath({ toggle, delay }) {
  // ...
  const animatedStyle = useSpring({
    // ...
    delay
  });

  // ...
}
```

Next, let's set a `delay` of `250` ms for the animation of `fill` in the `ShieldPart` component:
```javascript
function ShieldPart({ color, d, toggle }) {
  // ...
  const animationFillStyle = useSpring({
    // ...
    delay: 250
  });

  // ...
}
```

Finally, put a `delay` of `500` to the `useAnimatedPath` hook in the `Checkmark` component:

```javascript
function Checkmark({ toggle }) {
  const animationProps = useAnimatedPath({
    // ...
    delay: 500
  });

  // ...
}
```

> Note that the exact numbers we put to the `delay` property are somewhat arbitrary. There is no right or wrong way number, just watch the animation and fine-tune the exact numbers to your liking

Hit refresh in your browser and the animation should look like this 🎉👇
!["privacy" shield animation](https://dev-to-uploads.s3.amazonaws.com/i/yd7j9odsnexqdyfte3sr.gif)

You can find the full source-code for this article on [CodeSandbox!](https://codesandbox.io/s/react-svg-animation-3-4-y5v2n?file=/src/App.js:522-787)