May 20, 2011 12

CSS Animation Keyframe Events (Javascript solution)

By

Update (2011/06/14)
CSSA v0.2 is now available which adds support for Firefox 5 download.

CSS Animation Keyframe Events Title

I recently wrote a post about the lack of Javascript events for individual keyframes when using CSS3 Animations (read it here). To summarise you receive events when the animation starts (webkitAnimationStart) & when it finishes (webkitAnimationEnd) but not for each keyframe.

What if you want to trigger some arbitrary code as the 25% keyframe starts (like you can do with jQuery Runloop)? Natively you can’t but I decided to create a small Javascript function to help fill in the gaps (inspired by a comment by Altryne). The code is now ready to share so head on over to GitHub to read the doc’s and download the source. I’ve also created a small example to demonstrate it in action.

View Demo | Source @ GitHub

Requirements

A browser capable of rendering CSS3 Animations (Safari 5+, Chrome 12+, Firefox 5+).

Usage

Create the CSS animation keyframes in CSS as you would normally, for example for Safari/Chrome:

@-webkit-keyframes boxrotate {
  0% {
    -webkit-transform: translate3d(0, 0, 0);
    background: #da371e;
  }

  25% {
    -webkit-transform: translate3d(0px, 200px, 0) rotate(90deg);
    background: #da3ab9;
  }

  50% {
    -webkit-transform: translate3d(200px, 200px, 0) rotate(180deg);
    background: #34b6da;
  }

  75% {
    -webkit-transform: translate3d(200px, 0, 0) rotate(270deg);
    background: #88da50;
  }

  100% {
    -webkit-transform: translate3d(0, 0, 0) rotate(360deg);
    background: #da371e;
  }
}

Next trigger the animation on a specified DOM element:

var elem = document.getElementById('animateme');

// Trigger the animation named 'boxrotate' with duration 3000ms
CSSAnimation.trigger(elem, 'boxrotate', 3000);

There is also a jQuery plugin provided for convenience (cssanimation.jquery.js):

$('#animateme').cssanimation('boxrotate', 3000);

You can then listen for cssAnimationKeyframe events the same way you’d listen for any other. Here’s an example using jQuery:

$('#animateme').bind('cssAnimationKeyframe', function(event){
    var text = "";

    switch(event.originalEvent.keyText) {
        case '0%':
            text = "down ↓"; break;
        case '25%':
            text = "right →"; break;
        case '50%':
            text = "up ↑"; break;
        case '75%':
            text = "left ←"; break;
        case '100%':
            text = "click me"; break;
    };

    $('#text').html(text);
});

Limitations

Currently events will only be fired for keyframes at 5% increments (e.g. 0%, 5%, 10% etc). So if you have a keyframe at 23%, you won’t be notified. This is similar in design to how jQuery Runloop works.

You can change this if you want but you may begin to miss events! If you really want to change this you can by passing in an option to either the native function or jQuery plugin:

// Native
CSSAnimation.trigger(elem, 'boxrotate', 3000, {
    base: 1 // Raise events for keyframes at 1% increments
});

// jQuery
elem.cssanimation('boxrotate', 3000, {
    base: 1 // Raise events for keyframes at 1% increments
});

How does it work?

Using the requestAnimFrame() shim by Paul Irish we can get very accurate callbacks (@60fps), this means that:

The browser can optimize concurrent animations together into a single reflow and repaint cycle, leading to higher fidelity animation. For example, JS-based animations synchronized with CSS transitions.

This enables us to work out when a keyframe ought to have occured and raise a suitable event.