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
property, the first number gives the length of the dashes and the second gives the length of the gaps. Thestroke-dasharray
property specifies where the dasharray starts. By changing thestroke-dashoffset
from the path length to 0 using astroke-dashoffset
animation we can move the dashes about (try clicking on the dashed mug).@keyframes
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
values to the path length and we've got a coffee mug that draws itself:stroke-dasharray
.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
that does this for you:getTotalLength
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 conditionscupcakeAnimations.forEach(function(animation) {animation.path.style.transition = "none"; // Clear previous transition => fast removalanimation.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 herecupcakeAnimations[0].path.getBoundingClientRect();// Run line animationsvar 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
and adding path class names to differentiate between lines and shade:fill-opacity
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 conditionscupcakeFadeAnimations.forEach(function(animation) {animation.path.style.transition = "none"; // Clear previous transition => fast removalanimation.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 herecupcakeFadeAnimations[0].path.getBoundingClientRect();// Run line animationsvar 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.