SVG Animations Using CSS and Snap.svg
Today I wanted to share with you the ease of animation in SVG with the tools we have available to us today. SVG is still one of those subjects that can scare away a lot of developers, but I'm here to show you hopefully just how easy it can be.
While doing my usual search on the internet for inspiration, I came across this great post on From Up North. As you can see, there are some beautiful illustrations by Miguel Sousa aka Heymikel, Snack Studio, Martín Feijoó & Sara Enríquez. As soon as I saw the animations, the first thing that came to mind was how these could be made into SVG animations to be used freely on the internet.
Let's get to it
There are a few different implementations, some with the focus more on the Snap.svg side of things, then also combining both Snap.svg and CSS animations.
Preparation
The first part of any SVG project should be spent optimising your SVG's, hopefully in your case you have either created them yourself or will have a nice SVG supplied by your design team. Now rather than putting the vast subject of optimisation into this particular article. We recommend you check out this article by Sara Soueidan which has some great tools to help you out.
The biggest thing that will make working with SVG a breeze is to be very organised with your layering and grouping. Remove all unused layers and group elements that you feel will be linked together in animation.
Setting up Snap.svg
The beauty of Snap.svg is that it is simple to get set up and gives us a tremendous amount of functionality out of the box to manipulate our SVG's. First step is to include it in our project; there are many ways to do this, which you can find here
Initialise Snap SVG element
// HTML <svg class="hill-valley article"></svg> // JS var s = new Snap('.hill-valley');
Load our external SVG
Snap.load('img/hill-valley.svg', function (response) { var hillValley = response; s.append(hillValley); });
Gotham City Animation
The best thing to do with all your animations first is take a step back and think about exactly how you are going implement the animation and what needs to happen.
So with this particular animation there are two main animations going on, one is the animation of the 'bat light' which if you look carefully also applies a clipping mask on to the text. The other, is the animation of the scene lighting up in relation to the 'bat light' turning on and flickering.
Scene lighting animation
We wanted to show you how easy it is to use CSS animations still to manipulate your SVG elements, so we decided for the lighting of this animation it would be perfect to show that.
We only add classes to the paths we wish to manipulate and then we are away by just creating keyframe animations. In the code example below I will only do this for WebKit, but you should make sure you have all the correct vendor prefixes.
.gotham__background { -webkit-animation: background-anim 7s infinite linear; } @-webkit-keyframes background-anim { 0%, 10%, 21%, 23%, 25%, 27%, 37%, 55%, 57%, 61%, 63%, 80% { fill: #333738; } 11%, 20%, 22%, 24%, 28%, 36%, 38%, 54%, 56%, 58%, 62%, 64%, 79% { fill: #6D6C6D; } }
Bat light animation
The central component of the background animation is that we take full advantage of SVG's clipping masks. Meaning, we can show the title text as soon as our clip path rolls over it. The animation itself is a pretty simple one; it is just a rotate and scroll concurrently. We do take advantage of the available easing algorithms built into snap.svg. For more information on what these algorithms do take a look at a CodePen I made here.
To create a clipping mask in SVG, we need to make sure that our path is defined in the clipping path SVG element, with an id attached to it. Then we apply the 'clip-path' attribute to the element we wish to mask and that sets up the clipping path. Here is what that code looks like:
<clipPath id="b"> <use xlink:href="#a" overflow="visible"/> </clipPath> <g clip-path="url(#b)"></g>
Now, let's get this animation sorted:
// rotateElems is the element we wish to rotate rotateElems = s.selectAll('.gotham__rotate') rotateElems.animate({ transform: 'r0,250,453 s1,1' }, 1500, mina.elastic);
Kings Landing
The Kings Landing animation has a few little tricks to make some of the animations feel more realistic. We will go into these further on, for now let's look at how we created the cloud animation and using snap.svg to dynamically add extra elements.
Clouds Animation
The beauty of SVG is that it allows us to reuse elements quickly. Whether it be a group, path or shape, it can even let you reference external resources (external resources have lower browser support). We can achieve this by using the use
element, this simply references other objects on the page using the xlink:href
attribute.
One thing to note, if you have fill or stroke attributes on the original path, these will also be on every element no matter what you have defined on the use element. Therefore, if you want to reuse parts and have individual attributes on them, then you are better to apply none to your master element and only apply to the individual elements.
As we are going to animate clouds in multiple sizes and positions, it's better that we let snap.svg manage that process rather than it be hard coded into the SVG. All we do in the SVG is create our cloud path to be copied using the 'use' element.
The following creates a defined amount of clouds in a random layout, with an arbitrary scale:
var containerHeight = s.node.offsetHeight / 4, numberOfClouds = 10; // Gets the width of the container cloudWidth = s.select('#a').getBBox().w; // Creates a group element clouds = s.g(); // Loop to create clouds for (var i = numberOfClouds; i >= 0; i—) { /** x is a random number between 0 and the container width y is a random number between 0 and our container height newCloud creates a use element referencing our cloud path randomScale is a random number between 0.2 and 0.9 **/ var x = Math.floor(Math.random() * cloudWidth), y = -Math.floor(Math.random() * containerHeight), newCloud = cloud.use(), randomScale = Math.random() * (0.9 - 0.2) + 0.2; // Applies our new attributes to the use element newCloud.attr({ x: x, y: y, transform: 's' + randomScale }); // Adds the use element to our group clouds.add(newCloud); }
Animate along a path
One thing that snap.svg, doesn't do out of the box is give a method to allow you to animate over a particular path. It's not a massive issue though as we can utilise the Snap.animate method, this allows us to manipulate the animation frame by frame.
All we now need to do is get the path we wish to animate along. Then with a little bit of code, get its points at each frame of the animation and apply them to the element being animated. Here is the function:
/** path is the path we wish with to animate along element is the element we want to animate start is the frame we wish to start the animation on dur is the duration in milliseconds callback is a function we wish to call once the animation has finished **/ animateAlongPath = function (path, element, start, dur, callback) { // Get the path length, so we know how many frames we will animate over var len = Snap.path.getTotalLength(path); Snap.animate(start, len, function (value) { // movePoint gets the path attributes at a certain frame var movePoint = Snap.path.getPointAtLength(path, value); // applies the attributes to our element element.attr({ cx: movePoint.x, cy: movePoint.y }); }, dur, mina.easeinout, function () { callback(path); }); };
Hill Valley
The animation for the Hill Valley SVG has four principal components, with this particular animation we will use the easing algorithms provided by Snap.svg.
Car animation
This animation is just a simple translation combined with a rotation. The only thing that makes it more complex is because of the easing; it can make it appear difficult to achieve.
/** car is our SVG car element carStartMatrix and carMidMatrix initialises our Snap Matrix **/ var car = s.select('.car'), carStartMatrix = new Snap.Matrix(), carMidMatrix = new Snap.Matrix(); // Sets up the matrix transforms carStartMatrix.rotate(10); carStartMatrix.translate(0,-50); carMidMatrix.rotate(-15); carMidMatrix.translate(300,-20); car.animate({ transform: carStartMatrix }, 1250, mina.easeinout, function () { car.animate({ transform: carMidMatrix }, 250); });
Tree animation
The tree animation is a two part rotate animation to get more of a realistic bend during the animation. If the leaves were the same colour we could have used a path transform for the animation, but in our case the two-part animation was the better option.
It's a pretty simple concept, all we do is animate the whole tree by a small amount, and then at the same time we animate the leaves of the tree further. We can also take full advantage of the excellent easing algorithms that snap.svg has built into it. Here is how to achieve that:
/** leaves are the leaves element we want to rotate leavesDim is the bounding box dimension of leaves tree is the tree element we want to rotate treeDim is the bounding box dimension of the tree **/ var leaves = s.select('leaves'), leavesDim = leaves.getBBox(); leaves.animate({ transform: 'r25,' + (leavesDim.x + (leavesDim.width / 2)) + ',' + (leavesDim.y + leavesDim.height) }, 20, mina.easeinout, function (){ // This animation triggers once the other has finished leaves.animate({ transform: 'r0,' + (leavesDim.x + (leavesDim.width / 2)) + ',' + (leavesDim.y + leavesDim.height) }, 1000, mina.elastic); }); tree.animate({ transform: 'r8,' + (treeDim.x + (treeDim.width / 2)) + ',' + (treeDim.y + treeDim.height) }, 20, function () { // This animation triggers once the other has finished tree.animate({ transform: 'r0,' + (treeDim.x + (treeDim.width / 2)) + ',' + (treeDim.y + treeDim.height) }, 1000, mina.elastic); });
Clock animation
The clock animation is a relatively straightforward operation. The only thing you have to be careful with rotations is that if it rotates 360 degrees or more, then a further rotate applied; the animation will go the wrong direction.
You can see this in our following animation routine, let us take it that this line of code is being called in a loop. As you can see, we reset the rotated transform, so the animation keeps resetting.
var s.select('.minute'); // Resets to 0 clockMinute.transform('r0,195.5,105.5'); // Animates 360 degrees around the point 195.5,105.5 over 1250 ms clockMinute.animate({ transform: 'r90,195.5,105.5' },1250)
Text animation
The structure for the text animation is relatively straightforward; we just create five 'use' elements which reference the main text. Then on queue we trigger an animation which translates all the elements linearly to the top right of the initial text element.
/** textiles selects all of the .text elements, this is stored as an array amount is the max translation value divided by text elements on the page **/ var textElems = s.selectAll('.text'), amount = 20/textElems.length; // Loops through each element for (var i = 1; i < textElems.length; i++) { // Initiates the animation to translate to the correct position textElems[i].animate({ 'transform': 't' + (amount * i) + ',-' + (amount * i) }, 200, mina.easeinout); };
Hopefully, that has given you a bit of insight into how easy it is to animate SVG and create striking imagery. If you have any questions, please do not hesitate to get in touch via all the links below. The wonderful thing about SVG animation is it will happily work in all modern browsers and IE9 upwards. As you will see in my animations above where I use keyframe animation of CSS, you can just use snap.svg to do the same.
About Michael Tempest
Michael Tempest is the Lead Front End Developer at RefME. His passions are cycling, skiing, music and all things design / development related.
I’m made a pen with a 3D transform and a tiny trick in CSS http://codepen.io/benoitwimart/pen/YPBjpa
Don’t forget to check this
http://tympanus.net/Tutorials/SVGLoaderGSAP/
Great tutorial! I’m pretty new to Snapsvg but as I was cloning Snap’s Git Repo I noticed last update was about 10 months ago.
So the big question is whether Snapsvg is still active or should we use another framework like Vivus or Svgjs?
The last commit was actually four days ago, on the dev branch. It is very much alive and looked after by its creator Dmitry Baranovskiy and owned by Adobe.
Any other questions let me know.
In Firefox, only the first demo works. The other two throw errors which you can see in the console.
The one thing I haven’t figured out is how to detect browser support. None of the code above works in Firefox 5 for example but Firefox 5 does understand SVG and CSS animations and transformations, only applying those rules to SVG doesn’t work. Firefox 5 is not exactly a popular browser anymore but the question is rather how to figure out when a browser supports CSS-animated SVG and when not.
The Gotham City animation doesn’t work in Firefox 42.
I can see it in Chrome.
In IE 11, only Hill Valley works.
So: beware when using these nifty features!
Great examples, anyway.