Animated Animals in CSS and SVG

Learn some interesting animation techniques involving Sass and SVG filters for realistic motion effects on the example of animated animals.

AnimatedAnimals_800x600



View demo Download source

Today we want to show you how the clever use of HTML, CSS sequenced animations, and SVG filters can bring to life one of the most unlikely (yet adorable) things to be seen on a web page – animals. We’ll explore two techniques for drawing the animals: one with plain HTML and CSS, and one with inline SVG background images.

This demo is highly experimental – animated SVG filters are currently only available in Chrome.

The animations involved are also complex, so this tutorial will focus on the different techniques involved in creating each of these creatures and their life-like movements. It’s up to you to let your creative juices flow and create unique and playful animated animals on your own.

With that said, let’s get started!

Shaping the Animals

The demos use two different techniques for creating the shapes of the different body parts of the animals. The husky uses CSS border-radius properties, and the fox uses inline background SVG images, as the shapes are more complex.

The Markup

Both animals use nested HTML divisions to group the body parts. The concept of grouping is important for creating life-like movements — when the head moves, the eyes and ears should always move too, as they are attached to the head.

<!-- Markup for the fox head -->
<div class="fox-head">
  <div class="fox-face">            
    <div class="fox-ears">
      <div class="fox-ear"></div>
      <div class="fox-ear"></div>
    </div>
    <div class="fox-skull"></div>
    <div class="fox-front"></div>
    <div class="fox-eyes"></div>
    <div class="fox-nose"></div>
  </div>
</div>

<!-- Markup for the husky head -->
<div class="husky-head">
  <div class="husky-ear"></div>
  <div class="husky-ear"></div>
  <div class="husky-face">
    <div class="husky-eye"></div>
    <div class="husky-eye"></div>
    <div class="husky-nose"></div>
    <div class="husky-mouth">
      <div class="husky-lips"></div>
      <div class="husky-tongue"></div>
    </div>
  </div>
</div>

Each part can move independently, and will move with its parent element as it does so, which creates a more realistic effect. You’ll notice that the tail is deeply nested within other tail components. When each tail part is positioned relative to its parent, and then rotated the same amount, it creates the illusion of an even curve.

Screen Shot 2016-03-12 at 11.09.22 PM

Shaping with CSS

The border-radius property is used heavily for shaping the husky with CSS. For many elements, individual control of each border radius was needed. For example, here is how the husky’s hind leg was shaped:

.husky-hind-leg {
  // ...
  border-top-left-radius: 35% 100%;
  border-top-right-radius: 40% 100%;
}

The first number indicates how deep the curve starts on the top/bottom edge, and the second indicates how deep the curve starts on the left/right edge.

Other shapes, such as the front legs, could not be shaped with border-radius alone, and needed to be shaped using transform:


.husky-front-legs > .husky-leg:before {
  transform: skewY(-30deg) skewX(10deg);
  transform-origin: top right;
}

Once the shapes are in place, each element is given an absolute, percentage-based position inside its parent. This ensures precise placement of each body part, as well as responsiveness.

Shaping with SVG

For the fox, Sass-SVG was used to create the complex SVG shapes for each body part. SVG images can be used as background images, and even better, they can be written inline as long as they are either base-64 or UTF-8 encoded (for maximum browser support).

Writing them by hand is tricky, though. I used Adobe Illustrator to create the initial shapes:

Screen Shot 2016-03-12 at 11.17.26 PM

And then I saved each body part as an SVG image. The SVG code was transferred to the SCSS stylesheet via Sass-SVG. For example, this is the fox’s nose:

.fox-nose:before {
  @include svg((viewBox: (0 0 168 168))) {
    // the nose
    @include svg('path', (
      fill: $color-nose,
      d: 'M83.7,86.7c3.3,0,11.6-3.9,11.6-7.1c0-3.2-9.4-3.2-11.6-3.2c-2.2,0-11.6,0-11.6,3.2   C72.1,82.8,80.4,86.7,83.7,86.7z'
    ));

    // the line connecting the nose to the mouth
    @include svg('path', (
      stroke: $color-nose,
      fill: none,
      d: 'M83.7,102.3V86.7'
    ));

    // the mouth
    @include svg('path', (
      stroke: $color-nose,
      fill: none,
      d: 'M94.5,104.9c0,0-5.2-2.7-10.8-2.7c-5.6,0-10.8,2.7-10.8,2.7'
    ));
  }
}

This will generate an encoded, inline SVG string inside of a `url()` that looks something like this:

.fox-nose:before {
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg...");
}

Since the SVG is a background image, it can be transformed and animated as if it were an HTML element. With Sass-SVG, Sass $variables can be used to have full control of the SVG fill and stroke colors.

Making the fox responsive was straightforward with inline SVG. The viewbox attribute values ((viewBox: (0 0 168 168))) are straight from the SVG file, but as long as the height/width ratio is preserved, the element containing the SVG background image can be any size. All parts of the fox’s head were positioned absolutely, with the same height and width as the .fox-head.

“Squigglevision” with SVG Filters

Squigglevision is an animation technique that simulates hand-drawn animation by wiggling the outlines of a shape. This makes scenes, such as the fox and husky, seem more dynamic and sketched, even when the animals aren’t moving.

SVG has a filter called <feTurbulence> that gives “noise” to whatever it’s applied to. The <feDisplacementMap> filter is combined to specify how far away the pixels should move in each filter.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <filter id="squiggly-0">
      <feTurbulence id="turbulence" baseFrequency="0.02" numOctaves="3" result="noise" seed="0"/>
      <feDisplacementMap id="displacement" in="SourceGraphic" in2="noise" scale="2" />
    </filter>
    <filter id="squiggly-1">
      <feTurbulence id="turbulence" baseFrequency="0.02" numOctaves="3" result="noise" seed="1"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="3" />
    </filter>
    
    <filter id="squiggly-2">
      <feTurbulence id="turbulence" baseFrequency="0.02" numOctaves="3" result="noise" seed="2"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="2" />
    </filter>
    <filter id="squiggly-3">
      <feTurbulence id="turbulence" baseFrequency="0.02" numOctaves="3" result="noise" seed="3"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="3" />
    </filter>
    
    <filter id="squiggly-4">
      <feTurbulence id="turbulence" baseFrequency="0.02" numOctaves="3" result="noise" seed="4"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="1" />
    </filter>
  </defs> 
</svg>

Each filter has slightly different attributes. These filters can be applied to any element using the CSS filter: url(...); property. To create the “squigglevision” effect, a keyframe animation sets one filter at a time in rapid succession.


@keyframes squigglevision {
  0% {
    -webkit-filter: url('#squiggly-0');
    filter: url('#squiggly-0');
  }
  25% {
    -webkit-filter: url('#squiggly-1');
    filter: url('#squiggly-1');
  }
  50% {
    -webkit-filter: url('#squiggly-2');
    filter: url('#squiggly-2');
  }
  75% {
    -webkit-filter: url('#squiggly-3');
    filter: url('#squiggly-3');
  }
  100% {
    -webkit-filter: url('#squiggly-4');
    filter: url('#squiggly-4');
  }
}
Attention: These SVG filters do not seem to work in Firefox currently, so treat filter animations such as this one as a progressive enhancement.

Animating the Animals

CSS keyframes do not provide us a convenient way of sequencing and composing animations. The best way to approach this problem is to plan (storyboard) your animation as a timeline and use a preprocessor such as Sass to generate the keyframes.

For the fox, transforms and absolute time offsets (seconds) were used to animate each body part after outlining the storyboard for when each animation should occur. Here’s an example of how the fox’s nose was outlined in SCSS:

$animations: (
  // ...

  'nose': (
    // resting position
    (4s, 5s, 7s): rotateY(-4deg),

    // nose down
    4.5s: rotateY(-4deg) rotateX(-3deg),

    // fox looks left
    (7.5s, 9s): rotateX(-3deg) rotateY(-28deg) rotateZ(-11deg),

    // fox looks right
    (9.5s, 12s): rotateY(7deg),

    // fox looks straight ahead
    13s: rotateY(0),
  ),

  // ...
);

Here, $animations is a Sass map where the key is the name of the animation (e.g. 'nose'). The value of each animation name is another map where the key is the offset or list of offsets in seconds (e.g. (7.5s, 9s)), and the value is the transform property for each offset key.

So, how do we turn this map into @keyframe animations? First, a global $duration: 17s variable is set — this will be the total duration for each animation. Then, using nested Sass @each ... in ... loops, we can generate the expected CSS @keyframe declarations for each animation by looping through the $animations map:

@each $animation-name, $animation in $animations {
  // keyframe declaration
  @keyframes #{$animation-name} {
    @each $offsets, $transform in $animation {
      @each $offset in $offsets {
        // offset declaration block    
        #{percentage($offset / $duration)} {
          // transform property
          transform: #{$transform};
        }
      }
    }
  }
}

This will generate keyframes that look like this:

@keyframes nose {
  14.70588% {
    transform: rotateY(-4deg); }
  23.52941% {
    transform: rotateY(-4deg); }
  29.41176% {
    transform: rotateY(-4deg); }
  41.17647% {
    transform: rotateY(-4deg); }
  26.47059% {
    transform: rotateY(-4deg) rotateX(-3deg); }
  44.11765% {
    transform: rotateX(-3deg) rotateY(-28deg) rotateZ(-11deg); }
  52.94118% {
    transform: rotateX(-3deg) rotateY(-28deg) rotateZ(-11deg); }
  55.88235% {
    transform: rotateY(7deg); }
  70.58824% {
    transform: rotateY(7deg); }
  76.47059% {
    transform: rotateY(0); } }

These percentages can be very tedious to calculate without the use of SCSS. They represent the percentage offset of each desired time value in each step of our animation, relative to the total $duration.

Animations can then be applied to their respective body parts, e.g. animation: nose $duration none infinite;. It’s important that every animation has the exact same duration, so that they can be looped seamlessly.

Life-like Easing Curves

Another important part of making animations seem life-like is carefully choosing (or creating) easing curves for each part of the animation. The best kinds of lively easing curves to use are “sinusoidal” curves — in other words, easing curves that smoothly undulate in and out. Natural movements don’t come to a hard start or stop, so the animation-timing-function should reflect that.

For the fox and husky, I used cubic-bezier(0.645, 0.045, 0.355, 1) (preview here). This curve (seen below) has the qualities of starting slightly quickly but coming to a smooth stop. As always, it’s best to experiment with the curves to find the one that best fits your animation.

Screen Shot 2016-03-12 at 11.26.28 PM

One last thing: in Chrome, you can visually inspect all your sequenced animations to ensure that they occur at the correct times. Just open the console, click the Style tab, and click the play button:

AnimatedAnimals_Timeline

Hopefully this tutorial inspired you to create your own sequenced CSS animations of animals and more!

Browser Support:
  • ChromeSupported
  • FirefoxSupported
  • Internet ExplorerNot supported
  • SafariNot supported
  • OperaNot supported

References and Credits

View demo Download source

Previous:
Next:

Tagged with:

David Khourshid is a Florida-based web developer, tech author, and speaker. Also a fervent open-source contributor, he is passionate about JavaScript, CSS, Sass, functional programming, and cutting-edge front-end technologies. He is a frequent speaker at various conferences and local front-end meetups. When not behind a computer keyboard, he’s behind a piano keyboard or traveling.

View all contributions by

Related Articles

Feedback 26

  1. 3

    The problem you’re seeing with Firefox appears to be because the filter URL references are resolved against the stylesheet document. That is, your URL references are being resolved as url(“http://tympanus.net/Tutorials/AnimatedAnimals/css/fox.css#squiggly-3″). This is the correct behavior per spec and the fact that this works in Chrome appears to be a Chrome bug, but we should probably fix the Web platform so that this sort of referencing is allowed to work.

    I’m pretty sure if you stick your SVG filters in a separate document and reference them there this will work in Firefox (at least in DevEdition/Nightly).

Follow this discussion

Leave a Comment

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>