In this guide we’ll learn how to create animations using SVG like the following checked state from a project I recently worked on at digital agency Elephant:

We’ll do this by creating the following loading indicator, created for another project I worked on at Elephant:

# Setting up the basics

We’re working in Illustrator, but you can probably do this in other vector software too. Load up your SVG and make sure to optimize it before starting this guide. You can read about optimizing SVG’s here.

Anyhoo, you should now have the following image before you:

First things first, we’re going to be handdrawing the animated part. This can also be done with the pen tool, but for this example I’ll use the brush tool:

Let’s draw the path… err, wait a minute! The cursor is blocked, which is something we will have to fix first:

This is fixed by creating a custom brush. Please note: this could be related to my — at time of writing — bare bones Illustrator set-up. If you don’t have the blocked brush problem: skip to the drawing part. If you have the problem however, click the following button to set-up a new custom brush:

Choose Calligraphic Brush:

Copy the following settings (if not already the default) and click OK:

You should have a new brush set-up in your Brushes panel. If you can’t see your Brushes panel, activate it by click in the menu bar: Menu barWindowBrushes

Click your new brush and notice the cursor is now not blocked anymore, allowing you to draw your animatable path:

Great success! On to animating.

# Drawing the animatable path

Now comes the fun part: drawing the animation. The following .GIF should show off the process as clear as possible:

What I did is draw, with the Brush tool, the animation path. Important to note here, is that I’m drawing the path in the direction of how it will be animating. Once again review the final .GIF:

Notice the direction of the path being “drawn”? It’s exactly the direction of how I’m drawing the animation path. If you mess up the direction of your animation path, fret not! You can invert it with the following option: Menu barObjectPathReverse Path Direction.

There is also the option of inverting it in code later on when working in anime.js, but I’ve had mixed results here so I wouldn’t recommend it.

Also important is overdrawing the path. Review the following screenshot (I’ve changed the drawn animation path from red to blue here to have the red indicating circles stand out):

Notice how I’m drawing a little further than the “source” graphic? That’s on purpose; it’s to make sure the animation isn’t choppy at the end and/or the entire source graphic gets masked.

# Masking the source graphic with the animatable path

Now that we have our animatable path drawn, we’re going to use it to mask our source graphic.

First, select the source graphic layer by clicking here:

Make sure the “Transparency” panel is visible. If it’s not yet visible, toggle it with the following option: Menu barWindowTransparency. When visible, click on the “Make Mask” option:

Notice the panel changing, by now showing a clipping mask (the square to the right is now active). This is what we’ll use as our animation:

Next, select the drawn animatable path layer:

And cut it with + X. It should dissappear. Next, click on the clipping mask square indicated here:

You should move to the clipping mask area, indicated by the root layer changing to the name: Opacity Mask (indicated with the top red circle in the next screenshot).

Paste the drawn animatable path layer with the following option: Menu barEditPaste in Place. The default keyboard shortcut is + + V. This should put the drawn animatable path layer in exactly the same position we cut it, but in the clipping mask area. Nice!

Please note I’ve also changed the drawn animatable path to a white stroke. This is required, so go ahead and change it. You should see the source graphic bleeding through, where you first saw your drawn animatable path.

The following step is the coolest, as indicated by the following .GIF:

You need the “Stroke” panel, so if it isn’t visible toggle it with the following option: Menu barWindowStroke. Next, go ahead and increase the stroke width for the drawn animatable path. You should see your source graphic slowly appear more & more, every increase in 1 px stroke width. Pretty cool huh?

Increase the stroke width until the entire source graphic is visible.

Go ahead and change the “Cap” and “Corner” options in the “Stroke” panel to the following options:

Cap: Rounded Cap
Corner: Rounded Join
1
2

Done! Time to export. Do it exactly like you’ve learned in the optimize SVG guide, with 1 small difference. Disable the Minify option, making it easier to make the upcoming required custom edits to the SVG code.

If you open up your freshly exported SVG, you should see something like the following:

<svg
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    viewBox="0 0 75 75"
>
    <defs>
        <mask
            id="a"
            x="10.73"
            y="10.76"
            width="54.53"
            height="53.24"
            maskUnits="userSpaceOnUse"
        >
            <path
                d="M64.3,30.93c.35-7.08-1.26-14.69-8.18-18.17C50.18,9.77,42.45,11,36,11.09c-3.32.05-6.65.08-10,.15A13.73,13.73,0,0,0,15.34,15.9c-5.14,5.6-4.67,13.34-4.53,20.48q.09,4.9.58,9.78c.4,4,.64,7.81,3.12,11.09,4.22,5.58,11.6,6.63,18.14,6.73a184.15,184.15,0,0,0,19.23-.67,14,14,0,0,0,8.39-4.13A10.73,10.73,0,0,0,62.89,54a12.73,12.73,0,0,0,.41-3.12c0-.52.05-.49,0,.09-.41.73-.19.18.66-1.63,1.5-3.73,1.9-8-.1-11.61-2.34-4.28-7-5.64-11.53-5.27-7.24.58-14.49-.73-21.72.08-3.89.44-7.7,3.14-7.5,7.5.16,3.69,3.32,8,7.5,7.5,4.57-.51,9.12-.15,13.69,0a60.91,60.91,0,0,0,6.62,0c.47,0,2.49.1,2.93-.24-.4.3-3.4-3-3.48-3.23,0-.06-.13-1.25,0-1.22-.36-.08-1.84,5.85-2,6.88-.23,1.79,0,.45,0,.25l-.09.39c-.42,1.5.28.71,2.08-2.38-.25.11-.45.34-.69.47-1.61.87,1-.39,1.37-.14-.55-.34-2.81.11-3.41.13l-3.41.13c-2.43.09-4.86.2-7.29.28-2.24.07-4.49.12-6.74,0a12.8,12.8,0,0,1-2-.23c-.34-.07-.68-.16-1-.25s-.67-.19-1-.31l.52.21-.29-.16c-.46-.28-.41-.21.14.22.15,0-1.07-.74-.27-.22.58.37.08.91.33.2.06-.18-.11-.63,0-.23-.37-2.41-.49-4.89-.63-7.32A108.52,108.52,0,0,1,26,26.59c.15-2,.16-.32-.08-.08.23-.23.73-.59.86-.88.39-.82-.95.31-1.23.51-.71.52-.25.06.48.1s1.35,0,2,0l4-.06c5.19-.08,10.4-.34,15.59-.22a11.13,11.13,0,0,1,1.63.08c-.92-.16.25.06.36.19,0,0-.55-.33-.65-.4-.8-.59.29.48.09,0-.29-.66.14.46.15.49.31,1,.1.13.15.82a32.29,32.29,0,0,1-.05,3.81,7.56,7.56,0,0,0,7.5,7.5,7.66,7.66,0,0,0,7.5-7.5Z"
            />
        </mask>
    </defs>
    <title>Animating SVG paths</title>
    <polygon points="0 75 75 75 75 0 0 0 0 75" fill="#ffff03"/>
    <g mask="url(#a)">
        <path
            d="M53.4,30.4V26a4.23,4.23,0,0,0-4.2-4.2H26A4.31,4.31,0,0,0,21.7,26h0V49.1a4.23,4.23,0,0,0,4.2,4.2H49.1a4.23,4.23,0,0,0,4.2-4.2V43.3H41.2v3.8L31.6,40l9.6-7.1v3.8H60.1V49.1A10.84,10.84,0,0,1,49.2,59.9H26A10.84,10.84,0,0,1,15.1,49.1h0V26A10.84,10.84,0,0,1,26,15.2H49.2A10.9,10.9,0,0,1,60.1,26v4.4Z"
            fill="#2c2c2e"
        />
    </g>
</svg>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

If we simplify this a little, we have the following components:

<svg> <!-- Root SVG, leave alone -->
    <defs> <!-- Element that holds our clipping mask -->
        <mask id="a" ...> <!-- The clipping mask, that contains our drawn animatable path -->
            <path d="M64.3..."/> <!-- The drawn animatable path -->
        </mask>
    </defs>
    <title>Animating SVG paths</title>
    <polygon points="0 75 ..." .../> <!-- Background for our SVG -->
    <g mask="url(#a)"> <!-- A group containing our source graphic that has a mask applied -->
        <path d="M53.4..." fill="#2c2c2e"/> <!-- Our source graphic -->
    </g>
</svg>
1
2
3
4
5
6
7
8
9
10
11
12

See the <mask id="a" and <g mask="url(#a parts? Because Illustrator minifies our exported SVG to some extent, the ID for the mask is uglified to a.

ID’s in SVG work the same way like they do in HTML: you can only have 1 of each on a page. So if you export two animations in this same way and include them both on your page, you have the #a ID on your page twice! this === bad. Prevent getting yelled at by your W3C validator by changing your ID’s to something less common, like #logo-animation for instance.

# Animating the drawn animatable path with anime.js

Final step, finally! Time to animate with anime.js. Go ahead and inject your raw SVG code in your page. Something like the following:









 



<!doctype html>

<html>
<head>
    <title>Animating SVG paths</title>
</head>

<body>
    <svg><!-- Your SVG! --></svg>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11

Might I suggest CodePen here? I use it a lot for experimentation.

Okay, we still need to modify our SVG, following conventions in that I always add .js- prefixed classes to elements that we’re manipulating with JavaScript. Something like the following:







 













<svg>
    <defs>
        <mask id="a" ...>
            <!-- Note the added class below -->
            <path
                d="M64.3..."
                class="js-drawn-animation-path"
            />
        </mask>
    </defs>
    <title>Animating SVG paths</title>
    <polygon points="0 75 ..." .../>
    <g mask="url(#a)">
        <path
            d="M53.4..."
            fill="#2c2c2e"
        />
    </g>
</svg>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

It’s also very likely that Illustrator, when exporting, removed some of the key SVG presentation attributes on our drawn animatable path node. I haven’t found a way to consistently get these to export correctly, so for now inspect closely if all the expected SVG presentation attributes are there. For instance:

Stroke width: 15 pixelsstroke-width="15"

Very possibly Illustrator also moved the stroke properties to the fill. My exported SVG had a fill="#fff", when I was expecting a stroke="#fff". If this is your case, go ahead and manually change your attributes back.

Note from the editor
😣 Sorry this sounds so vague at this point. Illustrator sh*t the bed on me at this point, and I was under a pretty heavy deadline to get this guide done. I’ll rewrite this part once I get a chance to!

This final drawn animatable <path> should look something like this:

<path
    d="M56.8,31.32c0-3,0-3.08,0-5.21,0-4.29-4.43-8.22-10.34-8-4.45.15-6.88-.15-11.33-.24a62,62,0,0,0-7.34.14,15.1,15.1,0,0,0-5,1.23,7.55,7.55,0,0,0-3.7,3.5,12.13,12.13,0,0,0-.72,5.43l.34,15.55a28.32,28.32,0,0,0,.54,5.89,9.46,9.46,0,0,0,2.87,5.08c2.56,2.17,6.23,2.23,9.59,2.18l9.55-.13c2.15,0,4.29-.23,6.44-.26,6.15-.09,9.08-2.9,9.08-9.26,0-7.51-6.82-7.58-7.05-7.59A166.76,166.76,0,0,0,30.77,40"
    fill="none"
    stroke="#FFF"
    stroke-width="15"
    class="js-drawn-animation-path"
/>
1
2
3
4
5
6
7

Note the stroke, fill, stroke-width and class additions / changes. I wish it was easier than custom changes to SVG nodes; maybe some smart developer will come along and update this guide with an easier process!

Now, onto JavaScript. Below your SVG with a <script> tag, or in a new component following the conventions in our corporate framework. Target the drawn animatable <path> node:

const drawnAnimationPath = document.querySelector('.js-drawn-animation-path');
1

And below this, initialize anime.js:

anime({
  targets : drawnAnimationPath,
});
1
2
3

Now, review the anime.js documentation for declaring cool properties like a total animation time, looping and a custom timing function. I went with the following settings:

anime({
  targets    : drawnAnimationPath,
  duration   : 1250,
  easing     : 'easeInOutQuart',
  loop       : true,
  direction  : 'alternate',
});
1
2
3
4
5
6
7

Meaning my total animation will last 1250ms, will loop, has an alternate direction and has a custom easing function.

Last but not least, the magic tidbit! Best explained in the following CSS tricks post: https://css-tricks.com/svg-line-animation-works/

Add a strokeDashoffset animation property to anime.js:



 






anime({
  targets    : drawnAnimationPath,
  strokeDashoffset: [0, anime.setDashoffset],
  duration   : 1250,
  easing     : 'easeInOutQuart',
  loop       : true,
  direction  : 'alternate',
});
1
2
3
4
5
6
7
8

More information about anime.setDashoffset on the anime.js documentation site: https://animejs.com/documentation/#lineDrawing

And that’s it! You should now have something like this on your screen:

Nice job 🙌

# tl;dr: Show me the Codepen

Here ya go buddy 👍

# Recording the animation as a GIF

I’d like to wrap up this guide by offering a thought experiment. Take your newly created custom animation and ask yourself the question: “shouldn’t this be a .GIF animation?”.

The second .GIF in this guide clocks in a total 18kb. The anime.js library is 16.1kb. That’s almost exactly the same size, even excluding the source SVG that’s to be animated by anime.js. To make my point: in this example I would load the .GIF instead of the source SVG and the JS library, thus preventing any additional CPU overhead needed for the animation. The most efficient thing is loading a .GIF.

This is probably also true for the first example given in this guide, the grungy animated checked state. It might be worthwhile to optimize this animation to use .GIF, instead of the current way of animating.

There is a tipping point when using a .GIF becomes less ideal, that’s defined by multiple metrics:

  • Dynamic animations
    • Playing only parts of an animation, conditionally
    • Animate text that is modified at a later time, by CMS for instance
  • File size
  • Changing hue’s, animation tempo’s, etc.

# Kap

If however, you do decide to go the .GIF way, I recommend Kap. It’s a great app for recording your screen and exporting it as different file formats.