Interactive Design, Strategy, & Technology for Websites, Mobile, Apps, & Games

Dynamic Header

Share:

CSS3 Animations

View All Blog Posts

CSS3 Animations

– by Florin Dumitrescu

With the introduction of the CSS3 keyframe-based animations support, your web sites can now be spiced up with the presence of all kinds of interactive characters -- like our friend, Oog, shown below.

Let's jump in and start animating Oog's with some minimal markup. We're gonna start by making him walk:

<!DOCTYPE html> 
<html lang="en">
<head>
  <meta charset="utf-8" /> 
  <title>Oog's playground</title>  
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>
<head>
<body>
  <div class="cave">
    <div class="caveman">Oog</div>
  </div>
</body>
</html>

To set the mood, we're going to paint a terribly dark cave to place our character in:

.cave {
  float: left;
  background-color: #000;
  padding: 30px;
}
.caveman {
  text-indent: -9999px;
  overflow: hidden;
  font-size: 1px;
  background: url(oog.gif) 0 0 no-repeat;
  width: 60px; height: 73px;
}
p { 
  clear: both; 
  font-size: 11px; 
  font-family: helvetica, sans-serif;
  color: #999; 
  margin:0; padding: 10px 0; 
}

Oog's range of movement is encapsulated in the spritesheet referenced above, which looks like this:

Sprite credits: Ari Feldman, spriteLib

Next, we're using a new CSS3 rule called @keyframes.

The basic syntax looks like this:

@keyframes animationname {
  keyframe-selector1 { css-styles1; }
  keyframe-selector2 { css-styles2; }
  ...
  keyframe-selectorN { css-stylesN; }
}

Basically, this rule allows us to define percentage based keyframes within an animation timeline and defines the properties that should be applied during each frame.

The "animationname" value is just an identifier for this specific set of keyframes, so it can be set to pretty much anything you like.

Let's define the keyframe sequence that will allow Oog to walk around, leveraging the spritesheet and background set above in the "caveman" element. To achieve this we only need to change the background-position with every frame:

@keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}

We want every frame of his walk cycle to last the same amount of time, therefore we'll split the timeline evenly. If we wanted any of the frames in this animation to last longer than the others, we'd only have to tweak the percentages and make then uneven – eg. 0%, 10%, 50% ...

Now that we have the keyframes sequence defined, we'll need to set it up and apply it to an element. While we could apply the sequence to properties of the .caveman class, a better option is to create a new class that describes this behavior, the "walking":

.walk {
  animation-name: caveman-walk;
  animation-duration: 1.5s;  
  animation-iteration-count: 3;
}

Here's an explanation for these properties:

  • "animation-name" represents the keyframes sequence name we want to use for this specific animation
  • "animation-duration" is the time interval during which our keyframes sequence should play to the end – to speed it up, shorten this value (eg. 500ms), to  slow it down, increase it
  • "animation-iteration-count" is the number of times the keyframes sequence should loop until it stops – it can be also set to "infinite" for a continuous loop

If you run the code we're outlined so far, you'll notice that each frame slides smootly into place rather than flipping in directly. By default, all animations have an "eased" function applied to their transition. Fortunately, we can control this by changing the function using the animation-timing-function property:

.walk {
  animation-name: caveman-walk;
  animation-duration: 1.5s;  
  animation-iteration-count: 3;
  animation-timing-function: step-start;
}

Oog is nearly walking, but the code at this point doesn't appear to have any effect. This is because all the properties explained so far have not been yet implemented across the major browsers in an unified fashion, so we have to treat each browser we want to support separately and duplicate all these rules and properties using the common "-webkit-" and "-moz-" prefixes – to implement Safari/Chrome and Firefox support. Not the most elegant option, but until these properties are unified, we'll have to comply. So our Safari/Chrome and Firefox compatible implementation will end up looking like this:

/* future browser versions support */
@keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}
/* current safari/chrome support */
@-webkit-keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}
/* current firefox support */
@-moz-keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}

/* and the animation class */
.walk {
  animation-name: caveman-walk;
  -webkit-animation-name: caveman-walk;
  -moz-animation-name: caveman-walk;
  animation-duration: 1.5s;  
  -webkit-animation-duration: 1.5s;  
  -moz-animation-duration: 1.5s;  
  animation-timing-function: step-start;
  -webkit-animation-timing-function: step-start;
  -moz-animation-timing-function: step-start;
  animation-iteration-count: 3;
  -webkit-animation-iteration-count: 3;
  -moz-animation-iteration-count: 3;
}

Finally, we'll add a couple more animation classes and a jQuery snippet that allows us to loop through them, until the result looks like this:

View demo in new window.

Here is the JavaScript code that switches between the animations:

$(function(){
  
  var animations = Array("walk","charge","hit");
  var cidx = -1;
  
  function cycle_animation() {
    $(".caveman").remove();
    $(".cave").append("<div class='caveman' />");
    cidx++;
    if (animations.length-1 < cidx) cidx = 0;
    $(".caveman").bind("animationend webkitAnimationEnd", cycle_animation);
    $(".caveman").addClass(animations[cidx]);
    if (cidx == 0) {
      $caption = "peacefully walking...";
    } else if (cidx == 1) {
      $caption = "CHARGING!";
    } else {
      $caption = "HITTING!!!";
    }
    $(".caption").html("Oog is " + $caption);
  }

  cycle_animation();  
});

The core of this snippet is the binding of the "cycle_animation()" function to the "animationend" and the "webkitAnimationEnd" event of the "caveman" element.

These events are fired by Firefox and Safari/Chrome respectively at the end of the animation sequence cycle, according to the number of iterations we've set for it with the animation-iteration-count property.

Following is the entire listing of this example with the new classes and the JavaScript snippet in context.

Have fun animating!

<!DOCTYPE html> 
<html lang="en">
<head>
  <meta charset="utf-8" /> 
  <title>CSS3 Animation</title>  
  <script src="http://code.jquery.com/jquery-latest.min.js"></script>

<style type="text/css">
.cave {
  float: left;
  background-color: #000;
  padding: 30px;
}
p { clear: both; font-size: 11px; font-family: helvetica, sans-serif; color: #999; margin:0; padding: 10px 0; }

/* animation keyframes */
@keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}
@-webkit-keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}
@-moz-keyframes caveman-walk {
  0% { background-position: 0 0; }
  20% { background-position: -60px 0; }
  40% { background-position: -120px 0; }
  60% { background-position: -180px 0; }
  80% { background-position: -120px 0; }
  100% { background-position: -60px 0; }
}
@keyframes caveman-hit {
  0% { background-position: 0 -73px; }
  25% { background-position: -60px -73px; }
  50% { background-position: -120px -73px; }
  100% { background-position: 0 -73px; }
}
@-webkit-keyframes caveman-hit {
  0% { background-position: 0 -73px; }
  25% { background-position: -60px -73px; }
  50% { background-position: -120px -73px; }
  100% { background-position: 0 -73px; }
}
@-moz-keyframes caveman-hit {
  0% { background-position: 0 -73px; }
  25% { background-position: -60px -73px; }
  50% { background-position: -120px -73px; }
  100% { background-position: 0 -73px; }
}

/* animation classes */
.caveman {
  text-indent: -9999px;
  overflow: hidden;
  font-size: 1px;
  background: url(oog.gif) -60px 0 no-repeat;
  width: 60px; height: 73px;
  animation-timing-function: step-start;
  -webkit-animation-timing-function: step-start;
  -moz-animation-timing-function: step-start;
  animation-iteration-count: 3;
  -webkit-animation-iteration-count: 3;
  -moz-animation-iteration-count: 3;
}
.walk {
  animation-name: caveman-walk;
  -webkit-animation-name: caveman-walk;
  -moz-animation-name: caveman-walk;
  animation-duration: 1.5s;  
  -webkit-animation-duration: 1.5s;  
  -moz-animation-duration: 1.5s;  
}
.charge {
  animation-name: caveman-walk;
  -webkit-animation-name: caveman-walk;
  -moz-animation-name: caveman-walk;
  animation-duration: 300ms;  
  -webkit-animation-duration: 300ms;  
  -moz-animation-duration: 300ms;  
  animation-iteration-count: 5;
  -webkit-animation-iteration-count: 5;
  -moz-animation-iteration-count: 5;
}
.hit {
  animation-name: caveman-hit;
  -webkit-animation-name: caveman-hit;
  -moz-animation-name: caveman-hit;
  animation-duration: 1s;  
  -webkit-animation-duration: 1s;  
  -moz-animation-duration: 1s;  
  animation-iteration-count: 2;
  -webkit-animation-iteration-count: 2;
  -moz-animation-iteration-count: 2;
}
</style>

<script type="text/javascript">
$(function(){
  
  var animations = Array('walk','charge','hit');
  var cidx = -1;
  
  function cycle_animation() {
    $('.caveman').remove();
    $('.cave').append('<div class="caveman" />');
    cidx++;
    if (animations.length-1 < cidx) cidx = 0;
    $('.caveman').bind('animationend webkitAnimationEnd', cycle_animation);
    $('.caveman').addClass(animations[cidx]);
    if (cidx == 0) {
      $caption = 'peacefully walking...';
    } else if (cidx == 1) {
      $caption = 'CHARGING!';
    } else {
      $caption = 'HITTING!!!';
    }
    $('.caption').html('Oog is ' + $caption);
  }

  cycle_animation();  
});
</script>

</head>  
<body> 
  <div class="cave">
    <div class="caveman">Oog</div>
  </div>
  <p class="caption"></p>
  <p class="credit">Sprite credits: Ari Feldman, <a href="http://www.widgetworx.com/widgetworx/portfolio/spritelib.html" target="_blank">SpriteLib</a></p>
</body> 
</html>