SVG Path Animations

Hey that animation was pretty cool, right? Just click on him again to re-run it. Today's post is on how to create awesome SVG line animations like the one above.

SVG Paths

The SVG path format can seem rather cryptic:

<svg id="coffee" class="coffee" xmlns="http://www.w3.org/2000/svg" width="250" height="375" viewBox="0 0 250 375">
<path style="fill:none;stroke:#000000;stroke-width:5;"
d="m 19.892717,176.8917 1.190429,139.87515 c 3.078475,25.1789 11.076205,36.7905 41.069744,43.45065 21.81719,4.35025 40.50001,2.4318 60.11656,2.38085 29.56052,-2.41735 45.03841,-16.4321 47.61711,-41.06975 4.77735,-29.65355 -2.57891,-63.04595 2.3117,-95.7242 2.34095,-4.81735 1.6138,-10.7567 11.3782,-12.6047 4.893,-0.9099 8.9718,-0.73415 12.60465,0.035 7.01395,2.64455 11.4282,6.27795 13.7335,12.4634 2.27135,9.325 1.9101,18.65 1.04165,27.97505 -0.6871,6.0357 -2.34285,11.2697 -4.6129,15.32675 -5.56075,6.92775 -13.47125,11.10545 -19.3264,17.11985 -1.76685,2.77765 -1.1949,6.44065 -1.05965,9.21835 0.5008,3.07045 2.91735,4.2252 5.3569,5.3569 3.42525,0.73005 6.89725,1.2265 10.71385,0 7.5242,-2.499 12.99665,-7.5627 19.0468,-11.90425 4.9011,-5.3231 8.77275,-12.36185 12.4995,-19.64205 3.12195,-6.4744 3.27535,-14.4331 4.1665,-22.0229 -0.119,-7.5046 -0.4315,-14.9127 -1.33925,-22.02295 -4.8731,-19.68355 -18.54195,-35.8845 -38.54005,-39.28405 -5.1125,-0.42 -11.90225,0.1125 -20.622,-1.12135 -5.4145,-2.00515 -6.06795,-5.30365 -6.1265,-8.78685 0.78285,-7.14255 -0.6129,-14.4956 -4.20258,-21.63815 -6.66151,-9.72085 -18.17041,-13.45375 -31.54633,-14.88035 -22.53748,-3.28225 -43.72335,-3.05035 -64.28306,-1.1904 -9.30141,0.7866 -18.504954,1.1328 -28.570254,5.3569 -2.97607,1.27915 -5.95214,2.72185 -8.9282,5.95215 -1.224895,4.1665 -1.133765,8.333 2.38085,12.4995 7.77138,5.5689 15.83991,9.5035 24.403764,10.71385 15.9467,2.91015 32.12713,5.0412 49.25393,4.01765 13.53319,-1.55345 27.199,-2.84165 37.64724,-10.565 7.41696,-3.9148 9.57699,-9.80095 10.71385,-16.0708 -1.1491,-6.68715 -2.97648,-13.34485 -13.0947,-19.64205 -10.03057,-6.67185 -20.92652,-10.1706 -32.14153,-12.4995 -17.96892,-2.87915 -35.80275,-1.9664 -52.974,-4.46395 -16.962344,-2.0638 -17.978594,-7.64395 -17.558814,-14.582746 -1.55086,-4.59945 7.83395,-12.36765 13.68992,-12.2019 10.898904,-1.20005 22.995044,-2.5708 38.986494,-1.488 10.18657,2.1145 20.57079,4.03135 31.54631,5.3569 19.23428,-1.2312 23.56941,-6.5704 24.40375,-12.4995 0.76904,-5.48375 0.84257,-11.20525 -4.16649,-17.856405 -3.3469,-3.83015 -8.16664,-6.482 -15.47555,-7.14255 -25.80935,-0.76405 -51.29432,5.6476 -79.758614,5.95215 -18.34454,-0.4225 -32.777509,-4.74995 -38.837699,-15.4756 -0.686105,-6.3489 -1.214195,-12.99545 7.737779,-19.3444 7.163605,-5.637 17.07073,-10.58535 28.9326,-12.3071"
/>
</svg>

In reality nobody sane edits path elements by hand, and the best way to change them is with the use of an SVG editor like Inkscape. I create my SVGs in Inkscape then cut out the "bloat elements" Inkscape adds that aren't needed.

Single Path Animations

We'll start with a simple single path SVG:

.coffee-dashed path {
stroke-dasharray: 20 20;
stroke-dashoffset: 1547;
animation: coffee-dash 20s linear infinite;
}
@keyframes coffee-dash {
100% {
stroke-dashoffset: 0;
}
}

We can add dashes to an SVG path with the use of the

stroke-dasharray
property, the first number gives the length of the dashes and the second gives the length of the gaps. The
stroke-dashoffset
property specifies where the dasharray starts. By changing the
stroke-dashoffset
from the path length to 0 using a
@keyframes
animation we can move the dashes about (try clicking on the dashed mug).

Now imagine we made the dashes and gaps the length of the path, such that we only ever see one dash. To do this we increase the

stroke-dasharray
values to the path length and we've got a coffee mug that draws itself:

.coffee-drawself path {
stroke-dasharray: 1547 1547;
stroke-dashoffset: 1547;
animation: draw 15s linear infinite;
}
@keyframes draw {
0% {
stroke-dashoffset: 1547;
}
75%,
100% {
stroke-dashoffset: 0; /* Pause for 75%-100% of animation */
}
}

But wait, how do you know the path length? It turns out there is a handy JavaScript method

getTotalLength
that does this for you:

var path = document.querySelector(".coffee-drawself path");
var length = path.getTotalLength(); // 1546.7664794921875

Multi-path Animations

To do multi-path animations we can reproduce what we did for single line animations in JavaScript, then repeat that over all the paths in the SVG. Try clicking on this cupcake:

var cupcake = document.querySelector("#cupcakeDrawSelf");
var cupcakePaths = Array.from(cupcake.querySelectorAll("path"));
var cupcakeAnimations = cupcakePaths.map(function(path) {
var pathLength = path.getTotalLength();
var duration = Math.pow(pathLength, 0.5) * 0.03;
return { path, pathLength, duration };
});
function runCupcakeAnimations() {
// Initial conditions
cupcakeAnimations.forEach(function(animation) {
animation.path.style.transition = "none"; // Clear previous transition => fast removal
animation.path.style.strokeDasharray = `${animation.pathLength} ${animation.pathLength}`;
animation.path.style.strokeDashoffset = animation.pathLength;
animation.path.getBoundingClientRect(); // Trigger reflow of each path
});
// Triggering a reflow on first path so we animate from here
cupcakeAnimations[0].path.getBoundingClientRect();
// Run line animations
var begin = 0;
cupcakeAnimations.forEach(function(animation) {
animation.path.style.transition = `stroke-dashoffset ${animation.duration}s ${begin}s ease-in-out`;
animation.path.style.strokeDashoffset = "0";
begin += animation.duration + 0.1; // Slight 0.1s delay for drawing effect
});
}
cupcake.addEventListener("click", runCupcakeAnimations);

Finally if you want to make things really snazzy you can add fade effects by varying

fill-opacity
and adding path class names to differentiate between lines and shade:

var cupcakeFade = document.querySelector("#cupcakeDrawselfFade");
var cupcakeLines = Array.from(cupcakeFade.querySelectorAll(".line"));
var cupcakeShades = Array.from(cupcakeFade.querySelectorAll(".shade"));
var cupcakeFadePaths = [...cupcakeLines, ...cupcakeShades]; // Run shades last.
var cupcakeFadeAnimations = cupcakeFadePaths.map(function(path) {
var pathLength = path.getTotalLength();
var duration = Math.pow(pathLength, 0.5) * 0.03;
return { path, pathLength, duration };
});
function runCupcakeFadeAnimations() {
// Initial conditions
cupcakeFadeAnimations.forEach(function(animation) {
animation.path.style.transition = "none"; // Clear previous transition => fast removal
animation.path.style.strokeDasharray = `${animation.pathLength} ${animation.pathLength}`;
animation.path.style.strokeDashoffset = animation.pathLength;
animation.path.style.fillOpacity = "0";
animation.path.getBoundingClientRect(); // Trigger reflow of each path
});
// Triggering a reflow on first path so we animate from here
cupcakeFadeAnimations[0].path.getBoundingClientRect();
// Run line animations
var begin = 0;
cupcakeFadeAnimations.forEach(function(animation) {
animation.path.style.transition = `stroke-dashoffset ${animation.duration}s ${begin}s ease-in-out, fill-opacity ${animation.duration}s ${begin}s ease-in-out`;
animation.path.style.strokeDashoffset = "0";
animation.path.style.fillOpacity = "1.0";
begin += animation.duration + 0.1; // Slight 0.1s delay for drawing effect
});
}
cupcakeFade.addEventListener("click", runCupcakeFadeAnimations);

As always the full code for all these animations is on my GitHub.

Further reading

© George Pearson 2024