Masking SVG Animations
Using Clip-Path to make your animations more beautiful
If you’re like me, and starting to tire of the perfectly-linear, SVG line animations stampeding through the web at the moment, add this little trick to your tool belt and let’s get creative!
Here it is in action on one of ultranoir’s latest sites, ‘Over The Hills’, with a looney tunes flavour.
This sort of effect would normally be achieved using image sequences, but with SVG it gives me a range of benefits:
- I was easily able to duplicate the animation and change the colour to create a shadow — and then animate the scale and position of each independently;
- I didn’t have to preload a large animation sprite, nor create a custom animation loop to iterate between frames;
- if any changes were requested, I could make them directly in the code, provided with insanely precise timing controls through the Greensock animation library;
- and finally, I was able to merge this line animation in a timeline along with all of my other animations, letting me maintain a concise global control.
When it comes down to it, it’s really as simple as adding a clipping mask to your already-existing SVG animations. Let’s delve into the details.
Step 1: Creating the assets
This is by far the most crucial step — if you’re having problems getting it to work, more than likely it’s because your SVG assets weren’t correctly exported.
Start off by creating your mask shape. This is what will be visible when the animation is complete. I used Illustrator, so I will be speaking in reference to that, but I’m sure any vector-creation software will do the trick just as well.
Before you can export your mask, it is a good idea to merge all of the pieces into one single path. To do this, use the boolean operations in the pathfinder, or if the parts are separate, create a compound path, found in the object menu. Here’s a little Adobe explanation if you’re stuck.
Then draw your paths over the top, giving them a stroke-width wide enough to sufficiently cover the mask. These are the lines that will be animated, however they will only be visible within the bounds of the mask, giving the illusion that the mask itself is animating.
Now the example is covered in lines. Here I used 8 separate paths, paying attention that the lines didn’t reveal parts of the mask that they weren’t supposed to. This would ruin the illusion. If this is a real problem with your design, it may be necessary to split the animation into even more lines, or perhaps even having several separate masks, each with their own lines. You need to be a bit clever with your placement to get it working perfectly!
To really illustrate what I mean, here is an example where, when the line crosses itself, it unintentionally reveals more of the line than we want.
If I was really peeved by this, I could simply separate the animation into two parts, avoiding the crossover altogether.
Step 2: Formatting your SVG code
When you’re happy with your paths and mask, export them as SVG code. I won’t be showing the code for the dead pirates example because it’s much too long to read, but here is a simpler example.
<?xml version=”1.0" encoding=”utf-8"?>
<! — Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) →
<!DOCTYPE svg PUBLIC “-//W3C//DTD SVG 1.1//EN” “http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version=”1.1" xmlns=”http://www.w3.org/2000/svg" x=”0px” y=”0px” width=”100px” height=”100px” viewBox=”0 0 100 100">
<path d=”M48.28,7.333v82.838c0,1.381–1.117,2.496–2.495,2.496H14.762c-1.379,0–2.499–1.115–2.499–2.496V7.333H25.64v72.029h9.268V7.333H48.28z M65.1,92.667V20.64h9.265v72.027h13.374V9.83c0–1.379–1.117–2.497–2.497–2.497H54.219c-1.379,0–2.497,1.118–2.497,2.497v82.837H65.1z”/>
<path fill=”none” stroke=”#000000" stroke-width=”20" d=”M18.5,7c0,0,0,68.5,0,75.5s22.75,7,22.75,0C41.417,56.5,41.301,7,41.301,7"/>
<path fill=”none” stroke=”#000000" stroke-width=”20" d=”M81.5,93c0,0,0–68.5,0–75.5s-22.91–7–22.91,0c-0.166,26,0.029,75.5,0.029,75.5"/>
</svg>
Not the cleanest code to edit directly, but if you get out your glasses and look closely, you’ll see one path with quite a bit of data, then two smaller paths with a stroke applied. As you may have guessed, the first will become the mask, and the others are the paths to be animated. Our next step is to explain this to the computer.
Inside the SVG, we’re going to create a <defs> tag. Then insert a <clipPath> tag with an id of “myClip”, and wrap them around the mask path. Once comfortably wrapped, the mask path will no longer be visible.
Then we will add a ‘clip-path’ attribute to the animation paths, with a value of ‘url(#myClip)’, linking them to the clip path with the ID we just created.
Here’s one straight out of the oven.
<svg version=”1.1" xmlns=”http://www.w3.org/2000/svg" x=”0px” y=”0px” width=”100px” height=”100px” viewBox=”0 0 100 100">
<defs>
<clipPath id=”myClip”>
<path d=”M48.28,7.333v82.838c0,1.381–1.117,2.496–2.495,2.496H14.762c-1.379,0–2.499–1.115–2.499–2.496V7.333H25.64v72.029h9.268V7.333H48.28z M65.1,92.667V20.64h9.265v72.027h13.374V9.83c0–1.379–1.117–2.497–2.497–2.497H54.219c-1.379,0–2.497,1.118–2.497,2.497v82.837H65.1z”/>
</clipPath>
</defs>
<path clip-path=”url(#myClip)” fill=”none” stroke=”#000000" stroke-width=”20" d=”M18.5,7c0,0,0,68.5,0,75.5s22.75,7,22.75,0C41.417,56.5,41.301,7,41.301,7"/>
<path clip-path=”url(#myClip)” fill=”none” stroke=”#000000" stroke-width=”20" d=”M81.5,93c0,0,0–68.5,0–75.5s-22.91–7–22.91,0c-0.166,26,0.029,75.5,0.029,75.5"/>
</svg>
You can now place this code directly in your HTML.
Step 3: Animating with javascript
Now comes the icing on the cake. I will be using the Greensock TweenMax library along with good old jQuery. For the animation, it’s based upon a little genius CSS trick that Chris Coyier made universal in his famous article, in which only the path’s stroke-dashoffset needs to be animated in order to achieve the illusion of a line being drawn. Please read it if you haven’t, as it’s awesome, and basically because it’s hard to explain.
First things first, we store a reference to our paths, excluding our clip path, which is inside the ‘defs’ tag.
var paths = $(‘path:not(defs path)’);
Then for each path, we set the stroke-dasharray and stroke-dashoffset equal to the path’s total length, hence rendering it invisible (if that’s confusing, read the CSS Tricks article. Really).
paths.each(function(i, e) {
e.style.strokeDasharray = e.style.strokeDashoffset = e.getTotalLength();
});
Create a Greensock timeline, for ease of manipulation and the possibility to play the animation back and forth at the requested speed.
var tl = new TimelineMax();
Add each separate path animation to the timeline, animating the stroke-dashoffset to zero. Make use of the duration, delay and easing controls to construct the perfect animation.
tl.add([
TweenLite.to(paths.eq(0), 1, {strokeDashoffset: 0, delay: 0.0}),
TweenLite.to(paths.eq(1), 1, {strokeDashoffset: 0, delay: 0.5}),
]);
Then watch the magic unfold in your browser! You can download the entire example here.
Note that you could very well achieve this animation using pure CSS animations, or a custom requestAnimationFrame animation — I just have a preference for using TimelineMax as it’s awesome, and I’m able to keep my code as clean as humanly possible while maintaining an insane amount of control over every little element. A lot depends on the context of each situation, and for ‘Over The Hills’, as it was part of a much larger intro animation sequence, it was just an obvious choice to play it this way.
Hopefully you’ll be able to use this little trick one day and make your mum proud!