SVG Animations Using CSS and Snap.svg

By  on  
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.

Michael Tempest

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.

Recent Features

Incredible Demos

  • By
    Style Textarea Resizers

    Modern browsers are nice in that they allow you to style some odd properties.  Heck, one of the most popular posts on this blog is HTML5 Placeholder Styling with CSS, a tiny but useful task.  Did you know you can also restyle the textarea resizer in WebKit...

  • By
    Create a Download Package Using MooTools Moousture

    Zohaib Sibt-e-Hassan recently released a great mouse gestures library for MooTools called Moousture. Moousture allows you to trigger functionality by moving your mouse in specified custom patterns. Too illustrate Moousture's value, I've created an image download builder using Mooustures and PHP. The XHTML We provide...

Discussion

  1. ben

    I’m made a pen with a 3D transform and a tiny trick in CSS http://codepen.io/benoitwimart/pen/YPBjpa

  2. Deka

    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.

  3. Chris Harrington

    In Firefox, only the first demo works. The other two throw errors which you can see in the console.

  4. Fritz Lekschas

    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.

  5. 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.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!