What I have learned from using CSS3 Animations
First of all: CSS3 Animations are awesome!
I started using them to implement an idle animation on my portfolio page http://juerghunziker.ch.
My target was to make the Twitter bird on my page fly! I know, regarding the brand guidelines from Twitter I'm not allowed to do that. But hey, I did it anyway because... it just looks soo awesome! We're still friends Twitter? Right?
So where should I start? I used this tutorial to create the main flying animation (Thanks a lot!). The first thing I struggled to get the animation to work in my Chrome browser were the missing vendor prefixes in the animation part of the CSS. So I added them and my bird already flatted his wings. What I didn't know at this point: This was just the beginning of an upcoming vendor battle.
Vendor Prefixes
The first thing you have to do if you want to support the variety of the most common browsers out there is to implement CSS properties for all vendors. It looks like that Webkit is the last browser engine which doesn't support the unprefixed version of the animation properties. So we just have to add them to get it to work on all current browsers:
.twitter-bird-fly { -webkit-animation: fly 0.2s steps(3) 28; animation: fly 0.2s steps(3) 28; } @-webkit-keyframes fly { 0% { background-position: 0 0; } 100% { background-position: -90px 0; } } @keyframes fly { 0% { background-position: 0 0; } 100% { background-position: -90px 0; } }
Selecting CSS Rules
With the above piece of code and a second animation which moves my Twitter bird <div>-element to a chosen target position I already have done the biggest part of the work. That's what I thought...
Y U No stop moving 'target position'?
My target position is always at another coordinate of the page depending on the size and the shape of the browser window. So what I had to do is selecting the CSS keyframe rule and replace the 100%-subrule with the actual target position.
The cool part about this: You can access and change all CSS rules through a nice litte JavaScript API. So I have written a JavaScript function to select a CSS keyframe rule by name:
function getKeyframeRule(ruleName) { var ss = document.styleSheets, rule, i, x; for (i = 0; i < ss.length; ++i) { if(ss[i].cssRules) { // loop through all the rules (from bottom to top -> the most bottom matching rule is the applied rule) for (x = ss[i].cssRules.length - 1; x >= 0; --x) { rule = ss[i].cssRules[x]; if(rule.type === window.CSSRule.KEYFRAMES_RULE && rule.name === ruleName) { return rule; } } } } return null; }
But again there were some problems when using it in different browsers:
Hands off cross-origin stylesheets!
The first thing I had to learn the hard way was that Firefox doesn't allow you to access the cssRules-property of cross-origin stylesheets (expect CORS headers are set on remote machine). So we have to look for the keyframe in our local stylesheet only.
if(ss[i].href === styleSheetUrl && ss[i].cssRules)
Vendor Prefixes again...
Then there was another problem with the KEYFRAMES_RULE constant which isn't available in Webkit borwsers without its vendor prefix. What we have to do is obvious: Also check for WEBKIT_KEYFRAMES_RULE.
if((rule.type === window.CSSRule.KEYFRAMES_RULE || rule.type === window.CSSRule.WEBKIT_KEYFRAMES_RULE) && rule.name === ruleName)
The result
function getKeyframeRule(ruleName, styleSheetUrl) { var ss = document.styleSheets, rule, i, x; for (i = 0; i < ss.length; ++i) { if(ss[i].href === styleSheetUrl && ss[i].cssRules) { // loop through all the rules (from bottom to top -> the most bottom matching rule is the applied rule) for (x = ss[i].cssRules.length - 1; x >= 0; --x) { rule = ss[i].cssRules[x]; if((rule.type === window.CSSRule.KEYFRAMES_RULE || rule.type === window.CSSRule.WEBKIT_KEYFRAMES_RULE) && rule.name === ruleName) { return rule; } } } } return null; }
Et voilà: A function which works in all common browsers!
Chhhh..chh.. We're going deeper. Over.
Now that we have selected the keyframe rule we want to change the 100%-subrule. Again I wrote a litte JavaScript function which does exactly this:
function adjustBirdMoveAnimation(birdMoveRule) { birdMoveRule.deleteRule("100%"); birdMoveRule.insertRule("100% { -webkit-transform: scaleX(-1); transform: scaleX(-1); top: " + calculateDistanceY() + "px; left: " + calculateDistanceX() + "px; }"); }
This is what works in most of the browsers. But we want to get it to work in all browsers! So I had to do some modifications.
Internet Explorer respects standard specifications?!
I was really surprised when Internet Explorer told me that "100%" is not a valid parameter for the deleteRule()-function. A short look at the specifications at the Mozilla Developer Network confirmed what IE said: The method does only accept numbers between 0 (for 0%) and 1 (for 100%) as parameter.
So in our example we have to call the deleteRule()-function with the parameter 1 in IE to delete our 100%-rule. All other browser are also accepting a percentage value as string. (Some of them even only accept this parameter type!)
So we have to distinguish the implementation for IE browsers from all other browsers. To find out if your site is viewed with an IE browser just grab a random browser recognition function/library from the internet. I personally used the one from this StackOverflow answer: http://stackoverflow.com/a/16657946
// In IE the deleteRule function only accepts numbers from 0 to 1 if(myLib.isIE()) { birdMoveRule.deleteRule(1); } else { birdMoveRule.deleteRule("100%"); }
But all the cool kids have it (even Internet Explorer!)
It seems that Firefox again didn't want to make friends with the other browsers and implemented an appendRule()-function instead of the insertRule()-function. So what we have to do is checking if an appendRule()-function is available and using this instead of the insertRule()-function.
// Check if appendRule function is available (for Mozilla browsers) (see: https://developer.mozilla.org/en-US/docs/Web/API/CSSKeyframesRule) if($.isFunction(birdMoveRule.appendRule)) { birdMoveRule.appendRule(newAnimationEndRule); } else { birdMoveRule.insertRule(newAnimationEndRule); }
The result
function adjustBirdMoveAnimation(birdMoveRule) { var newAnimationEndRule; // In IE the deleteRule function only accepts numbers from 0 to 1 if(myLib.isIE()) { birdMoveRule.deleteRule(1); } else { birdMoveRule.deleteRule("100%"); } newAnimationEndRule = "100% { -webkit-transform: scaleX(-1); transform: scaleX(-1); top: " + calculateDistanceY() + "px; left: " + calculateDistanceX() + "px; }"; // Check if appendRule function is available (for Mozilla browsers) (see: https://developer.mozilla.org/en-US/docs/Web/API/CSSKeyframesRule) if($.isFunction(birdMoveRule.appendRule)) { birdMoveRule.appendRule(newAnimationEndRule); } else { birdMoveRule.insertRule(newAnimationEndRule); } }
Most people listen to music, I listen to events
Thanks to the mentioned Animation JavaScript API it is possible to do something after an animation has ended. And surely I had to find a use for this as soon as I heard of it. So I wanted to show a Follow me! speech bubble after the bird has landed.
// show followme tooltip after bird move animation is done $('#twitter-bird-box').one('animationend', showFollowMeTooltip );
And a new JavaScript snippet was born... which worked in almost no browser :( You can imagine why...
Vendor prefixes! (For the last time now)
We also have to listen to the prefixed versions of the animationend event like this:
// show followme tooltip after bird move animation is done $('#twitter-bird-box').one('webkitAnimationEnd oanimationend MSAnimationEnd animationend', showFollowMeTooltip );
Almost there!
Okay, I now have a bird animation that works in all commonly used browsers. But there was one problem left: Once the animation has finished it never played again until I reloaded the page.
Restart animation
In my example removing the animation class and readding it worked pretty well. The fact that I always readd the animation class after a certain timeout makes it an easy solution... except you want it to make it work in IE. To accomplish this I had to hide all elements which have an animation with display: none; and reshow it.
// stop animation -> remove animation classes and hide elements $('#twitter-bird-box').removeClass('twitter-bird-move'); $('#twitter-bird').removeClass('twitter-bird-fly'); $('#twitter-bird').hide(); $('#twitter-bird-box').hide();
// start animation -> add animation classes and show elements $('#twitter-bird-box').show(); $('#twitter-bird').show(); $('#twitter-bird').addClass('twitter-bird-fly'); $('#twitter-bird-box').addClass('twitter-bird-move');
There are other (probably more performant) solutions to restart CSS3 animations described at css-tricks.com. You probably want to look into these too.
Habemus papam!
We finally have a flying Twitter bird which works in Chrome, Firefox, Safari, Opera, IE10+ and all the mobile browsers. The benefit that my page is hosted on GitHub is that you have the chance to look into the whole JavaScript and CSS sources of it: https://github.com/tschortsch/tschortsch.github.io
Now head over to http://juerghunziker.ch and go find my animation! Or just wait for it ;)












