---
title: SVG Animations
teaser: How to animate and manipulate SVGs.
tags: svg,bourbon,sass,design
author: Steven Harley
published_on: 2014-11-05
---

Once you’re familiar with [what SVGs are](https://thoughtbot.com/blog/building-ralph-in-svg) and [how to
optimize and embed them](https://thoughtbot.com/blog/organized-workflow-for-svg), 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](http://bourbon.io). 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](https://images.thoughtbot.com/animating-svg/logo-red.svg) and there are three separate animations I’d like to tackle:

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

Once the [SVG is embedded](https://thoughtbot.com/blog/organized-workflow-for-svg) in the markup, we can start styling it. Add a class to each element you’d like to manipulate, just like in <abbr title="HyperText Markup Language">HTML</abbr>, starting with the red background circle in the logo:

```xml
...
<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](http://bourbon.io) 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 <abbr title="Cascading Style Sheets">CSS</abbr> `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](https://bugzilla.mozilla.org/show_bug.cgi?id=923193) 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:

```scss
@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;
  }
}
```

```scss
%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](http://css-tricks.com/snippets/css/keyframe-animation-syntax/).

This is what we should have so far:

_Hover over the box below to trigger the animation._

<div class="example-container svg-animation">
  <svg version="1.1" class="ralph" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
    viewBox="0 0 480.6 468.3" enable-background="new 0 0 480.6 468.3" xml:space="preserve">
    <g>
      <path class="ralph__background" fill="#C32F34" d="M236.3,57.5c-71.1,0-128.8,57.7-128.8,128.8c0,71.1,57.7,128.8,128.8,128.8c71.1,0,128.8-57.7,128.8-128.8
        C365.1,115.1,307.4,57.5,236.3,57.5z" />
    </g>
  </svg>
</div>

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 <abbr title="Cascading Style Sheets">CSS</abbr> 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.

```scss
@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;
  }
}
```

```scss
.ralph__head {
  @include animation(moveInDown 0.5s ease-out forwards);
  @extend %center-and-fade;
}
```

```scss
@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;
  }
}
```

```scss
.ralph__body {
  @include animation(moveInUp 0.5s ease-out forwards);
  @extend %center-and-fade;
}
```

_Hover over the box below to trigger the animation._

<div class="example-container svg-animation">
  <svg class="ralph" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
    viewBox="0 0 480.6 468.3" enable-background="new 0 0 480.6 468.3" xml:space="preserve">
    <g>
      <path class="ralph__background" fill="#C32F34" d="M236.3,57.5c-71.1,0-128.8,57.7-128.8,128.8c0,71.1,57.7,128.8,128.8,128.8c71.1,0,128.8-57.7,128.8-128.8
        C365.1,115.1,307.4,57.5,236.3,57.5z" />
      <g>
        <path class="ralph__body" fill="#FFFFFF" d="M202.8,192.7h-5.2v36.6h0c1.7,2.1,2.8,4.7,2.8,7.6c0,3.6-1.6,6.8-4.2,9l-3.2-3.2c1.8-1.3,2.9-3.4,2.9-5.8
          c0-4-3.3-7.3-7.3-7.3s-7.3,3.3-7.3,7.3c0,2.4,1.1,4.5,2.9,5.8l-3.2,3.2c-2.5-2.2-4.2-5.4-4.2-9c0-2.9,1.1-5.6,2.8-7.6v-57
          c0.1-2.2,1.9-4,4.1-4l104.5,0c2.2,0,4,1.8,4.1,4v57c1.8,2.1,2.8,4.7,2.8,7.6c0,3.6-1.6,6.8-4.2,9l-3.2-3.2
          c1.7-1.3,2.9-3.4,2.9-5.8c0-4-3.3-7.3-7.3-7.3s-7.3,3.3-7.3,7.3c0,2.4,1.1,4.5,2.9,5.8l-3.2,3.2c-2.6-2.2-4.2-5.4-4.2-9
          c0-2.9,1.1-5.6,2.8-7.6h0v-36.6h-5.2v77.2l3.5,2v6.3h-35v-6.3l3.5-2h0v-23.4h-10.4v23.4l3.5,2v6.3l-34.9,0.1v-6.3l3.4-2
          L202.8,192.7z" />
        <g class="ralph__head">
          <path fill="#FFFFFF" d="M262,121.5h-53.1c-2.2,0-4,1.8-4,4v34.6c0,2.2,1.8,4,4,4H262c2.2,0,4-1.8,4-4v-34.6
              C266,123.3,264.2,121.5,262,121.5z M221.9,146.9c-3.7,0-6.8-3-6.8-6.8c0-3.7,3-6.8,6.8-6.8c3.7,0,6.8,3,6.8,6.8
              C228.7,143.9,225.7,146.9,221.9,146.9z M249.1,146.9c-3.7,0-6.8-3-6.8-6.8c0-3.7,3-6.8,6.8-6.8c3.7,0,6.8,3,6.8,6.8
              C255.9,143.9,252.8,146.9,249.1,146.9z" />
          <path fill="#FFFFFF" d="M225.4,140.1c0,1.9-1.5,3.5-3.5,3.5c-1.9,0-3.5-1.6-3.5-3.5c0-1.9,1.6-3.5,3.5-3.5
              C223.9,136.7,225.4,138.2,225.4,140.1z" />
          <path fill="#FFFFFF" d="M252.6,140.1c0,1.9-1.6,3.5-3.5,3.5c-1.9,0-3.5-1.6-3.5-3.5c0-1.9,1.5-3.5,3.5-3.5
              C251,136.7,252.6,138.2,252.6,140.1z" />
        </g>
      </g>
    </g>
  </svg>
</div>

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

```scss
@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;
  }
}
```

```scss
.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);
}
```

_Hover over the box below to trigger the animation._

<div class="example-container svg-animation">
  <svg class="ralph" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 480.6 468.3" enable-background="new 0 0 480.6 468.3" xml:space="preserve">
    <g>
      <path class="ralph__background" fill="#C32F34" d="M236.3,57.5c-71.1,0-128.8,57.7-128.8,128.8c0,71.1,57.7,128.8,128.8,128.8c71.1,0,128.8-57.7,128.8-128.8
        C365.1,115.1,307.4,57.5,236.3,57.5z" />
      <g>
        <path class="ralph__body" fill="#FFFFFF" d="M202.8,192.7h-5.2v36.6h0c1.7,2.1,2.8,4.7,2.8,7.6c0,3.6-1.6,6.8-4.2,9l-3.2-3.2c1.8-1.3,2.9-3.4,2.9-5.8
          c0-4-3.3-7.3-7.3-7.3s-7.3,3.3-7.3,7.3c0,2.4,1.1,4.5,2.9,5.8l-3.2,3.2c-2.5-2.2-4.2-5.4-4.2-9c0-2.9,1.1-5.6,2.8-7.6v-57
          c0.1-2.2,1.9-4,4.1-4l104.5,0c2.2,0,4,1.8,4.1,4v57c1.8,2.1,2.8,4.7,2.8,7.6c0,3.6-1.6,6.8-4.2,9l-3.2-3.2
          c1.7-1.3,2.9-3.4,2.9-5.8c0-4-3.3-7.3-7.3-7.3s-7.3,3.3-7.3,7.3c0,2.4,1.1,4.5,2.9,5.8l-3.2,3.2c-2.6-2.2-4.2-5.4-4.2-9
          c0-2.9,1.1-5.6,2.8-7.6h0v-36.6h-5.2v77.2l3.5,2v6.3h-35v-6.3l3.5-2h0v-23.4h-10.4v23.4l3.5,2v6.3l-34.9,0.1v-6.3l3.4-2
          L202.8,192.7z" />
        <g>
          <g>
            <path class="ralph__wifi-bar" fill="#FFFFFF" d="M236,109.1c7.3,0,13.8,2.6,17.9,6.5l3.2-4.6c-5.1-4.5-12.6-7.4-21.1-7.4l0,0c-8.5,0-16.1,2.9-21.1,7.4
                l3.2,4.6C222.2,111.7,228.8,109.1,236,109.1" />
            <path class="ralph__wifi-bar" fill="#FFFFFF" d="M236.4,97.5c7.9,0,15.3,2.3,20.8,6.5c1.1,0.8,2.1,1.7,3,2.7l3.2-4.6c-6.2-6.1-16-10.1-27-10.1l0,0
                c-11.3,0-21.3,4.1-27.5,10.5l3.2,4.6c1-1.1,2.1-2.2,3.4-3.2C221.1,99.7,228.5,97.5,236.4,97.5" />
            <path class="ralph__wifi-bar" fill="#FFFFFF" d="M236,86.3c12.6,0,23.7,4.6,30.3,11.6l3.2-4.5c-7.7-7.6-19.9-12.5-33.5-12.5l0,0
                c-13.7,0-25.8,4.9-33.5,12.6l3.2,4.5C212.3,90.9,223.4,86.3,236,86.3" />
          </g>
          <g class="ralph__head">
            <path fill="#FFFFFF" d="M262,121.5h-53.1c-2.2,0-4,1.8-4,4v34.6c0,2.2,1.8,4,4,4H262c2.2,0,4-1.8,4-4v-34.6
                C266,123.3,264.2,121.5,262,121.5z M221.9,146.9c-3.7,0-6.8-3-6.8-6.8c0-3.7,3-6.8,6.8-6.8c3.7,0,6.8,3,6.8,6.8
                C228.7,143.9,225.7,146.9,221.9,146.9z M249.1,146.9c-3.7,0-6.8-3-6.8-6.8c0-3.7,3-6.8,6.8-6.8c3.7,0,6.8,3,6.8,6.8
                C255.9,143.9,252.8,146.9,249.1,146.9z" />
            <path fill="#FFFFFF" d="M225.4,140.1c0,1.9-1.5,3.5-3.5,3.5c-1.9,0-3.5-1.6-3.5-3.5c0-1.9,1.6-3.5,3.5-3.5
                C223.9,136.7,225.4,138.2,225.4,140.1z" />
            <path fill="#FFFFFF" d="M252.6,140.1c0,1.9-1.6,3.5-3.5,3.5c-1.9,0-3.5-1.6-3.5-3.5c0-1.9,1.5-3.5,3.5-3.5
                C251,136.7,252.6,138.2,252.6,140.1z" />
          </g>
        </g>
      </g>
    </g>
  </svg>
</div>

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.
