Mozilla

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?

5 comments

Comments are now closed.

  1. Peter van der Zee wrote on September 6th, 2011 at 03:58:

    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.

    1. Mook wrote on September 6th, 2011 at 21:35:

      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…

  2. Gerben wrote on September 6th, 2011 at 09:45:

    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.

    1. Chris Heilmann wrote on September 6th, 2011 at 10:17:

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

  3. Joe Lambert wrote on September 7th, 2011 at 02:54:

    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/

Comments are closed for this article.