Detecting and generating CSS animations in JavaScript

When writing of the hypnotic spiral demo the issue appeared that I wanted to use CSS animation when possible but have a fallback to rotate an element. As I didn’t want to rely on CSS animation I also considered it pointless to write it by hand but instead create it with JavaScript when the browser supports it. Here’s how that is done.

Testing for the support of animations means testing if the style attribute is supported:

var animation = false,
    animationstring = 'animation',
    keyframeprefix = '',
    domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
    pfx  = '';

if( elm.style.animationName ) { animation = true; }

if( animation === false ) {
  for( var i = 0; i < domPrefixes.length; i++ ) {
    if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
      pfx = domPrefixes[ i ];
      animationstring = pfx + 'Animation';
      keyframeprefix = '-' + pfx.toLowerCase() + '-';
      animation = true;
      break;
    }
  }
}

[Update - the earlier code did not check if the browser supports animation without a prefix - this one does]

This checks if the browser supports animation without any prefixes. If it does, the animation string will be 'animation' and there is no need for any keyframe prefixes. If it doesn't then we go through all the browser prefixes (to date :)) and check if there is a property on the style collection called browser prefix + AnimationName. If there is, the loop exits and we define the right animation string and keyframe prefix and set animation to true. On Firefox this will result in MozAnimation and -moz- and on Chrome in WebkitAnimation and -webkit- so on. This we can then use to create a new CSS animation in JavaScript. If none of the prefix checks return a supported style property we animate in an alternative fashion.

if( animation === false ) {

  // animate in JavaScript fallback

} else {
  elm.style[ animationstring ] = 'rotate 1s linear infinite';

  var keyframes = '@' + keyframeprefix + 'keyframes rotate { '+
                    'from {' + keyframeprefix + 'transform:rotate( 0deg ) }'+
                    'to {' + keyframeprefix + 'transform:rotate( 360deg ) }'+
                  '}';

  if( document.styleSheets && document.styleSheets.length ) {

      document.styleSheets[0].insertRule( keyframes, 0 );

  } else {

    var s = document.createElement( 'style' );
    s.innerHTML = keyframes;
    document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );

  }

}

With the animation string defined we can set a (shortcut notation) animation on our element. Now, adding the keyframes is trickier. As they are not part of the original Animation but disconnected from it in the CSS syntax (to give them more flexibility and allow re-use) we can't set them in JavaScript. Instead we need to write them out as a CSS string.

If there is already a style sheet applied to the document we add this keyframe definition string to that one, if there isn't a style sheet available we create a new style block with our keyframe and add it to the document.

You can see the detection in action and a fallback JavaScript solution on JSFiddle:

JSFiddle demo.

Quite simple, but also a bit more complex than I originally thought. You can also dynamically detect and change current animations as this post by Wayne Pan and this demo by Joe Lambert explains but this also seems quite verbose.

I'd love to have a CSSAnimations collection for example where you could store different animations in JSON or as a string and have their name as the key. Right now, creating a new rule dynamically and adding it either to the document or append it to the ruleset seems to be the only cross-browser way. Thoughts?

About Chris Heilmann

Evangelist for HTML5 and open web. Let's fix this!

More articles by Chris Heilmann…


5 comments

  1. Peter van der Zee

    I think it should be WebKit (capital K), although they’re not very consistent. But I believe they want to go with the camel cased version where possible.

    September 6th, 2011 at 03:58

    1. Mook

      Actually, it’s a CSS to JS mapping thing – all dashes (-) are removed and the next character is turned into upper case, because – isn’t a valid character for a JavaScript property name. So “-webkit-animation-name” becomes “WebkitAnimationName”. Hence the ridiculous looking “Khtml” (from -khtml). I’m not sure why -ms didn’t turn into “Ms”, though. It doesn’t seem to be a mistake here; see -ms-transform ( http://msdn.microsoft.com/en-us/library/ff974936%28v=VS.85%29.aspx ) for a similar thing. Perhaps they just felt like being odd again…

      September 6th, 2011 at 21:35

  2. Gerben

    And what happens when browsers start to drop the prefix notation in favor of the official name? Then every site using this code snippet will stop showing animations. So long for forward compatible code.

    September 6th, 2011 at 09:45

    1. Chris Heilmann

      Excellent point! I fixed this by testing for non-prefix animation and changed the code. thanks for bringing this up.

      September 6th, 2011 at 10:17

  3. Joe Lambert

    Here’s a small JavaScript shim to enable the kind of JSON style interface you suggested at the end of your post: http://blog.joelambert.co.uk/2011/09/07/accessing-modifying-css3-animations-with-javascript/

    September 7th, 2011 at 02:54

Comments are closed for this article.