Animating SVG paths with GreenSock’s GSAPJS

Just recently Twitter has been buzzing about the impressive animation that Polygon.com used for their Xbox and PS4 review. After I saw it I wanted to find out whether I could do something similar.

Polygon.com animating SVGImage source: Polygon.com

So how would you go about writing code for animating SVG?

The theory is pretty simple. You would need to select the SVG Document then collect all the paths within it, calculate their total lengths then animate them. The SVG comes with stroke-dasharray and stroke-dashoffset properties: the first one controls the pattern of dashes and gaps in paths; the second controls the distance to the start of the dash pattern. Stroke-dasharray contains comma or white space separated length values that specify the length of dashes and spaces. By adding the same values to both properties we can make the SVG dissapear (or at least appear so). Once that is done all we really need to animate is the value of the stroke-dashoffset to give the illusion of lines drawing on the screen.

Easier said than done though, right? Not at all; it’s pretty straightforward.

Searching the internet revealed that the easiest way to do that is through the use of the <object> element and embedding the SVG in your document, but I decided not to go this way. Polygon inserts their SVG right in the document and then manages to animate the paths so I decided I’ll do the same.

Straight away I hit the first hurdle. Using the document.getElementsByTagName() or  document.getElementById() can select the SVG elements but you won’t have access to the methods that come with the SVG so calculating the path length may prove difficult. document.getElementsByTagNameNS() to the rescue! It does pretty much the same work that getElementsByTagName but returns the list of elements belonging to the given namespace. So let’s try doing that.

Animating SVG in 10 simple steps.

Let’s start off the build process by adding your SVG to the document, more or less this way:

<div id=”animation”>
    Paste your svg document here;
</div>

Now the document should show your image – you can click the image below to see what we are about to build.

Animating SVG Now let’s jump to the script; the next few lines of JavaScript is where the magic happens. For the purpose of this example I am using GreenSock’s GSAPJS, which is pretty much the best tweening library out there (at least in my opinion) but you can apply the same principles using jQuery Animate or using requestAnimationFrame() or setInterval().

Because there are multiple paths, I decided to use TimelineMax that is a part of TweenMax library. Let’s define and cache our animation timeline.

Step 1: Let’s select our SVG container.

var svgContainer = document.getElementById(“animation”);

Step 2: Let’s set up our SVG element namespace to use when selecting elements.

var nS = “http://www.w3.org/2000/svg”

(If you look in your SVG markup you’ll notice the xmlns property is the same)

Step 3: Let’s set up our animation timeline.

var animation = new TimelineMax();

Step 4: Let’s prevent animation from playing straight away.

animation.pause();

Step 5: Let’s select all the SVG paths.

var svgPaths = svgcontainer.getElementsByTagNameNS(ns,”path”)

Now the variable svgPaths hold an array of svg paths so we need to traverse through it, apply base styles and push the animation into the animation queue.

Step 6: Traverse through paths, apply base styles and push them onto the animation queue.

for (var x = 0; x < svgPaths.length; x++){
 //select a path
 var path = svgPaths[x];
 //get the pixel length of the SVG path
 var pathDimensions = svgPaths.getTotalLength();
 // apply styles to stroke-dasharray and stroke-dashoffset
 path.style.strokeDasharray = pathDimensions+” “+pathDimensions;
 path.style.strokeDashoffset = pathDimensions;
 animation.add(TweenMax.to(path.style,1,{strokeDashoffset:0});
 }

The above does exactly what it says on the tin: we apply base styles to the path (strokeDashoffset and strokeDasharray) that are the total pixel length of the given path and using the .add() method we push the TweenMax tween definition onto the queue. The number 1 in the TweenMax definition represents seconds. If you add the animation.play() after the for loop you should see that your paths are animating, but the order is sequential and each is triggered once the previous element in the queue finishes animating.

To fix that and just offset the animations a little bit, we can add the negative time offset to make them animate as the other elements in the queue are animating. It’s super easy with GSAP. You need to bear in mind that you wouldn’t want to offset the first path. The code below will account for that. “-=0.8” will tell the animation queue to start animating 0.2 seconds after the previous animation started 1-0.8 = 0.2.

Step 7: Offset the animations.

 for (var x = 0; x < svgPaths.length; x++){
 //select a path
 var path = svgPaths[x];
 //get the pixel length of the SVG path
 var pathDimensions = svgPaths.getTotalLength();
 // apply styles to stroke-dasharray and stroke-dashoffset
 path.style.strokeDasharray = pathDimensions+” “+pathDimensions;
 path.style.strokeDashoffset = pathDimensions;
 animation.add(TweenMax.to(path.style,1,{strokeDashoffset:0},(x>0)?”-=0.8”:””);
 }

Step 8: Play the SVG animation.

All you need to do now is to add the play call after the for loop.

 for (var x = 0; x < svgPaths.length; x++){
 //select a path
 var path = svgPaths[x];
 //get the pixel length of the SVG path
 var pathDimensions = svgPaths.getTotalLength();
 // apply styles to stroke-dasharray and stroke-dashoffset
 path.style.strokeDasharray = pathDimensions+” “+pathDimensions;
 path.style.strokeDashoffset = pathDimensions;
 animation.add(TweenMax.to(path.style,1,{strokeDashoffset:0},(x>0)?”-=0.8”:””);
 }
 animation.play();

Step 9: Solving some browser problems – Fixing it.

So,  you probably noticed that Firefox and Internet Explorer have some issues animating the paths (at least at the time of writing this article).

Firefox issue:

It seems that Firefox redraws the path multiple times during the animation.  It quickly became apparent that the strokeDashoffset value is wrong if you have used the path pixel length. The fix was actually pretty surprising and it came out to be the SVG stroke width. You will need to collect the path stroke width and divide the pathDimensions by it. Just add to your code.

var strokeWidth = (path.getAttribute(“stroke-width”) == null)?1:path.getAttribute(“stroke-width”);

then update your strokeDashoffset line to accommodate the above (just in Firefox).

path.style.strokeDashoffset = (/Firefox/i.test(navigator.userAgent))?pathDimensions/strokeWidth:pathDimensions;

Now Firefox should animate your SVG paths nicely and just once!

Internet Explorer issue:

You may have noticed that IE has a little bit of a problem with redrawing the SVG and parts of your image disappear on animation. The SVG object has forceRedraw() method but that seemed not to be the case. The fix for that is to force IE to redraw the whole document. Thankfully the TweenMax comes with onUpdate method in which you can control what happens each time the frame is drawn. Thomas Fuchs presents a very simple method to force the DOM redraw.

I’m going to use parts of it to achieve the required effect.

Animation.add(TweenMax.to(line.style,1,{strokeDashoffset:0,opacity:1,onUpdate:function(){
 var n = document.createTextNode(' ');
 document.body.appendChild(n);
 document.body.removeChild(n);
 }}),(x>0)?"-=0.8":"");

What the above code does is add the space character in the body element and then immediately removes it forcing the browser to redraw. Simple but very effective.

Step 10: TA DAH! You’re animating SVG paths!

That is it! Your code should now look more or less like this:

<script>
 var svgContainer = document.getElementById("animation");
 var nS="http://www.w3.org/2000/svg";
 var animation = new TimelineMax();
 animation.pause();
 var svgPaths = a.getElementsByTagNameNS(nS,"path");
 for(var x = 0; x<svgPaths.length;x++){
 var path = svgPaths[x];
 var pathDimensions = path.getTotalLength();
 var strokeWidth = path.getAttribute("stroke-width");
 path.style.strokeDasharray = (pathDimensions)+" "+(pathDimensions);
 path.style.strokeDashoffset = (/Firefox/i.test(navigator.userAgent))? pathDimensions/strokeWidth : pathDimensions;
 animation.add(TweenMax.to(path.style,1,{strokeDashoffset:0,onUpdate:function(){
 var n = document.createTextNode(' ');
 document.body.appendChild(n);
 document.body.removeChild(n);
 }}),(x>0)?"-=0.8":"");
 }
 animation.play();
 </script>

You will probably have noticed that this code is somewhat flawed and it only supports the “path” element of an svg and there is so much more than just paths to it. With simple alterations to the above code you can extend it so it supports more than just the path.