SVG Animations

Steven Harley

Once you’re familiar with what SVGs are and how to optimize and embed them, it’s time to dive in and start manipulating them. There are many ways to animate and manipulate SVGs; in this post, we’ll go over how to animate embedded SVGs with Sass and Bourbon. Animating SVGs with CSS gives us great browser support and uses properties already familiar to those already using CSS animations.

First, we’ll need a plan. What do we want to animate and what are the different steps of the animation? For this example, we’ll be animating the thoughtbot logo and there are three separate animations I’d like to tackle:

  1. Growing the background circle into view
  2. Putting Ralph’s head and body together
  3. Showing Ralph’s Wi-Fi bars power on

Once the SVG is embedded in the markup, we can start styling it. Add a class to each element you’d like to manipulate, just like in HTML, starting with the red background circle in the logo:

...
<path class="ralph__background" fill="#C32F34" d="..." />
...

You can find the right element to add your class to by inspecting the SVG in your browser and noting the attributes or position of the element within the SVG.

The Sass below is all using Bourbon which saves a lot of time and space writing all of the different vendor specific syntaxes. We’ll use Bourbon mixins to create the keyframes for each animation and to include the animation in our .ralph__background styles.

The first keyframe animation we’ll define will be called grow. It will contain steps, written as percentages, for a CSS transform to scale the background from small to large. In order to make it more fluid and realistic, we can add steps to scale the SVG more and less than 1, to make it feel as if it’s settling on the final value. We’ll also start with an opacity: 0, so it’s not visible before the animation begins and so it fades into view rather than just appearing. Be sure to put an opacity: 1 in final keyframe of each animation, so when the animation finishes, the element stays visible, more on that later.

For simplicity, all of the elements will have a transform-origin in the center. I’ll create a placeholder class for these properties, so I can reuse the code for all of the elements I’ll be animating. There is a bug in Firefox with SVGs and -moz-transform-origin, where 50% and center values don’t work as intended. To work around this you must define the origin in pixels.

So the Sass would look like this:

@include keyframes(grow) {
  0% {
    @include transform(scale(0.3));
  }

  30% {
    @include transform(scale(1.2));
  }

  60% {
    @include transform(scale(0.9));
  }

  100% {
    @include transform(scale(1));
    opacity: 1;
  }
}
%center-and-fade {
  @include transform-origin(50%);
  opacity: 0;
}

.ralph__background {
  @include animation(grow 0.5s ease-out forwards);
  @extend %center-and-fade;
}

So over the course of half a second, we start with a scale of 0.3, grow up to 1.2, then back down to 0.9, finally landing on 1. This felt good to me, but you could add more steps or change the percentages to suit your needs.

Then we simply use the animation mixin to reference the grow animation then define a duration, easing-function and an animation-fill-mode. A forwards animation-fill-mode will keep the element in the state it was in during the last keyframe. There are a lot of animation properties for more complex animations, for getting more familiar with them I recommend this CSS-Tricks snippet.

This is what we should have so far, hover over the box below to trigger the animation.

Next up, we can start putting Ralph together. Again, we need to give the SVG elements classes and create keyframes. Instead of scaling his head and body like the background circle, we can use the CSS transform translate3d to slide it into place. Even though we’re only translating the SVG on a 2D plane, it’s good to use translate3d because 3D translations will tell the browser to use hardware acceleration, resulting in a smoother animation.

Ralph’s head and body will come from different directions, so we’ll need two different animations, moveInUp and moveInDown. The two animations are almost identical, we just change the positive Y values to negative and vice versa to change the direction of the translation.

@include keyframes(moveInDown) {
  0% {
    @include transform(translate3d(0, -500px, 0));
  }

  60% {
    @include transform(translate3d(0, 12px, 0));
  }

  80% {
    @include transform(translate3d(0, -8px, 0));
  }

  100% {
    @include transform(translate3d(0, 0, 0));
    opacity: 1;
  }
}
.ralph__head {
  @include animation(moveInDown 0.5s ease-out forwards);
  @extend %center-and-fade;
}
@include keyframes(moveInUp) {
  0% {
    @include transform(translate3d(0, 500px, 0));
  }

  60% {
    @include transform(translate3d(0, -12px, 0));
  }

  80% {
    @include transform(translate3d(0, 8px, 0));
  }

  100% {
    @include transform(translate3d(0, 0, 0));
    opacity: 1;
  }
}
.ralph__body {
  @include animation(moveInUp 0.5s ease-out forwards);
  @extend %center-and-fade;
}

So far, each element is getting its own animation and all of the animations are triggered at once (on hover). For the final piece, after Ralph gets put together, we’ll be revealing each Wi-Fi bar above his head individually. We reuse the keyframes for all of the Wi-Fi bars and simply add a delay for each bar using nth-child.

@include keyframes(smallMoveInUp) {
  0% {
    @include transform(translate3d(0, 8px, 0));
  }

  50% {
    @include transform(translate3d(0, -2px, 0));
  }

  100% {
    @include transform(translate3d(0, 0, 0));
    opacity: 1;
  }
}
.ralph__wifi-bar {
  @include animation(smallMoveInUp 0.5s ease-out forwards);
  @extend %center-and-fade;
}

.ralph__wifi-bar:nth-child(1) {
  @include animation-delay(0.6s);
}

.ralph__wifi-bar:nth-child(2) {
  @include animation-delay(0.8s);
}

.ralph__wifi-bar:nth-child(3) {
  @include animation-delay(1s);
}

And there you have it, multiple animations for a single SVG. We broke down the animation into parts, wrote keyframes for each part and referenced them in our Sass file. Bourbon handles most of the heavy lifting with mixins, so you can focus on how you want the animation to turn out. These short, simple animations are a great way to start giving a little life to your graphics using Sass.