Keyframes and the Animate Method

by kirupa   |   27 July 2015

In an earlier tutorial, we looked at the Web Animations spec and its goal of unifying all the various animation approaches you have today into one single implementation. As part of that, we looked at the animate method which gives you all the performance benefits of animations created in CSS with the more dynamic-icity (I made up a whole new word!) of something you create in JavaScript.

In this tutorial, we are going to take a deeper look at the animate method by revisiting some of the important details we simply glossed over. This will be part review, part learning about new stuff, and a big part of living the dream...provided your dream was to learn more about the animate method.

Let's get started!

CSS Animations and the Animate Method

If you know how CSS animations are defined, you pretty much know everything there is to know about how the animate method works. The similarities between these two are uncanny, so let's take a look at that first. As you may know, your CSS animations are commonly defined in two parts. The first part is the animation declaration where you specify general properties about the animation itself:

#bigcloud {
    animation: bobble 2s infinite;
    margin-left: 100px;
    margin-top: 15px;
}

It is here where you point to the @keyframes rule that is responsible for detailing what exactly gets animated, how long the animation will take to run, how many times it will loop, and various other things that apply to your animation as a whole.

The second part (aka the fun part!) is where you define the keyframes that you associated as part of the animation declaration earlier. These keyframes contain the details that specify what your animation does:

@keyframes bobble {
    0% {
        transform: translate3d(50px, 40px, 0px);
        animation-timing-function: ease-in;
    }
    50% {
        transform: translate3d(50px, 50px, 0px);
        animation-timing-function: ease-out;
    }
    100% {
        transform: translate3d(50px, 40px, 0px);
    }
}

It is here where you set the various CSS properties you wish to animate along with the timing details of when each keyframe will become active. This @keyframes rule combined with the animation declaration results in (hopefully) something that you see getting animated.

The cool thing is this - if this animation were to be converted into JavaScript using the animate method, you'll see a lot of similarities. A LOT OF SIMILARITIES:

var animationPlayer = cloudElement.animate([
    {
      transform: "translate3d(50px, 40px, 0px)",
      easing: "ease-in"
    },
    {
      transform: "translate3d(50px, 50px, 0px)",
      easing: "ease-out"
    },
    {
      transform: "translate3d(50px, 40px, 0px)"
    }
  ], {
    duration: 2000,
    iterations: Infinity
  });

If you ignore the weird brackets and parenthesis and squint your eyes a little bit, the animate version and the CSS version probably look identical. You even specify the same kinds of information:

  1. The animation keyframes containing the various CSS-ish properties and values you wish to animate
  2. The properties that affect the animation overall

If you already knew a lot about CSS animations, this consistency means that the animate method is actually very easy to understand. If you didn't know a whole lot about CSS animations, hopefully this section enlightened you to both that as well as what you can do with the animate method. Everybody wins!

More About Keyframes

Now that we've celebrated how closely related CSS animations and the animate method are, let's shift back to our regularly scheduled programming and dive deeper into the animate method and the things about it you should know more about.

Specifying Multiple Properties

You've seen this already, but it doesn't hurt to emphasize this one more time. To add additional CSS properties, simply chain them together. Below is an example of the opacity and transform properties being specified:

var animationPlayer = circleElement.animate([
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)"
    },
    {
      opacity: 1,
      transform: "translate3d(300px, 0px, 0px)"
    },
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)"
    }
  ], {
    duration: 2000,
    iterations: Infinity
  });

From a syntax point of view, the CSS properties inside your keyframes are represented as object key and value pairs. Additional property statements (except for the last one) are separated by a comma.

Adjusting When Keyframes Play

By default, any keyframes you specify are evenly spaced apart. That probably makes no sense, so let me elaborate. Your first keyframe is always at the beginning of your animation. Your last keyframe is always the end of your animation. Any remaining keyframes are evenly spaced between the beginning and the end. The exact time a particular keyframe becomes active is determined on your animation's duration.

To help make all of this even more clear, the following diagram shows the mapping between keyframes and time for a 2 second animation with three keyframes:

At the beginning, you are at 0 seconds. The 100% keyframe represents the end of your animation, and that would occur at the 2 second mark - which is also your duration value. The 50% mark is half-way at 1 second. Simply multiply the keyframe offset value by the duration to get the exact time that keyframe becomes active.

Now, this even spacing is the default behavior. For many kinds of animations, there will be times when you want to deviate from the default behavior. You can deviate easily by setting the offset property:

var animationPlayer = circleElement.animate([
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 0
    },
    {
      opacity: 1,
      transform: "translate3d(300px, 0px, 0px)",
      offset: .9
    },
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 1
    }
  ], {
    duration: 2000,
    iterations: Infinity
  }); 

The offset property takes a number (not a percentage) between 0 and 1 with 0 representing the beginning and 1 representing the end. In the above example, notice that the 2nd keyframe occurs at the .9 (90%) mark. This means that this animation takes a long time (1.8 seconds) to get to the second keyframe but only takes .2 seconds to get from the second keyframe to the third.

By adjusting the offset values, you can add an extra level of time warping that goes beyond what you can simply accomplish with just easing functions. Speaking of which...

Easing Functions

The most important part of an animation is how the values change over a period of time. This important detail is controlled by something known as easing functions. I've written about (and recorded videos about) easing functions extensively across CSS and JavaScript, so I won't rehash what makes them awesome. Instead, let's look at how to use them with the animate method.

The easing functions you can set are:

The property you use to specify the easing function inside the animate method is the appropriately named easing property, and you can set its value to any of the easing functions you see listed above. There are two places you can specify the easing property.

First, you can set the easing property globally by specifying it as part of the general animation properties:

var animationPlayer = circleElement.animate([
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 0
    },
    {
      opacity: 1,
      transform: "translate3d(" + distance + "px, 0px, 0px)",
      offset: .9
    },
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 1
    }
  ], {
    duration: 2000,
    iterations: Infinity,
    easing: "ease-in-out"
  });    

This means that all keyframes will have their property changes be affected by this easing function - which is ease-in-out. If you want more precise control, the second place you can specify your easing property is within a keyframe itself:

var animationPlayer = circleElement.animate([
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 0
    },
    {
      opacity: 1,
      transform: "translate3d(" + distance + "px, 0px, 0px)",
      easing: "ease-in",
      offset: .9
    },
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 1
    }
  ], {
    duration: 2000,
    iterations: Infinity
  });

This allows you to alter the easing function used between keyframes, and you'll usually end up doing this as opposed to setting your easing function more generally. One thing you should note is that the easing functions are specified as a string value. This means you need to wrap them inside quotation marks. If you don't do that, your animation will not work. It's an easy detail to overlook :P

Dynamic Values!

The last thing we are going to look at is also one of the biggest differentiating factors in favor of the animate method over a typical CSS animation. With the animate method, the values you animate don't have to be static. Take a look at the following example:

var distance = Math.round(Math.random() * 1000);

var animationPlayer = circleElement.animate([
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 0
    },
    {
      opacity: 1,
      transform: "translate3d(" + distance + "px, 0px, 0px)",
      offset: .9
    },
    {
      opacity: 0,
      transform: "translate3d(0px, 0px, 0px)",
      offset: 1
    }
  ], {
    duration: 2000,
    iterations: Infinity
  });

More specifically, take a look at the highlighted lines. Notice that the x value for the translate3d transform is specified by the distance variable. The distance variable is defined outside of your animate declaration, and by having done this, you can do a whole lot more with it than just have a hard-coded value of 300.

All property values can be parameterized and set anywhere in your code, and this makes creating animations using the animate method often more dynamic than what you get with CSS animations and a whole lot simpler than defining everything manually using requestAnimationFrame. This is soooo cool, that we'll explore this further in a future tutorial.

Conclusion

If you've been asking Santa (or your parents) for an easy way to create animations in JavaScript that didn't involve you specifying every detail under the sun manually, the animate method is like a gift that keeps on giving. It's conceptually similar to CSS animations, so you don't have a huge new set of things to learn and keep track of. It's syntax is similar to CSS animations while staying true to the object literal syntax prevalent in JavaScript. It's quite easy to use once you get some of the basics down, but it contains a lot of bells and whistles that allow you to create some really elaborate animations. We'll look at some of those bells and whistles shortly.

If you have a question about this or any other topic, the easiest thing is to drop by our forums where a bunch of the friendliest people you'll ever run into will be happy to help you out!

THE KIRUPA NEWSLETTER

Get cool tips, tricks, selfies, and more...personally hand-delivered to your inbox!

( View past issues for an idea of what you've been missing out on all this time! )

GOT A QUESTION?

HOT FORUM TOPICS

Serving you freshly baked content since 1998!

Killer hosting by (mt) mediatemple

Facebook Twitter Youtube Pinterest Instagram Github
BACK TO TOP
new books - yay!!!