permalink

72

CSS3 Transition Animations With jQuery Fallbacks

Read Later submit to reddit

In today's post, we'll be taking a look at how you can use CSS3 transitions to power your application's animations with jQuery's .animate() method as a fallback where transitions aren't supported. The benefit of transitions is that unlike JavaScript based animations, they're hardware accelerated in certain browsers and mobile devices, resulting in a potentially smoother overall animation than you would normally experience. 

In a lot of cases your code will  work seamlessly with the solutions presented today, so the effort involved in applying them is minimal. Before we get started, if you'd prefer to see a sample of demos using today's fallbacks before reading the post, feel free to check some out below.

Demo Gallery

Superman sample animation scene View demo

Interactive demos

 

 

 

 

 

Basic bouncing-ball concept View demo

 

 

Introduction to CSS3 Transitions

For readers who haven't looked into transitions before, we're going to go through a quick overview of exactly what CSS3 transitions have to offer. Transitions are a part of the draft CSS3 specification and provide a means to animate changes in CSS properties rather than having those changes take effect instantaneously. 

With transitions, the computed value of a property transitions over time from the old value to the new value. What this means is if a script queries the computed style of a property as it is transitioning, it will see an intermediate value that represents the current animated value of the property.

Let's take a look at an example of transitions in action: if we wanted to change CSS properties such as the margin, padding and height of an element on hover, normally this would happen instantaneously. With transitions however, the change can occur over an interval of time using a number of short-hand easing equations. Sample code for how to achieve this with CSS3 transitions for a miniature gallery can be found below:

.note {
    padding:30px;
    color:#000;
    font-family: Arial, Helvetica, sans-serif;
    text-align:center;
    width:100px;
    height:150px;
    margin:10px;
    float:left;
    text-shadow:0px 1px 1px #fff;
    background: #83C8F7;
    background: -moz-linear-gradient(top, #83C8F7 0%, #207ce5 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#83C8F7), color-stop(100%,#207ce5));
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#83C8F7', endColorstr='#207ce5',GradientType=0 );
    box-shadow:0px 7px 14px #666666;
    border-top:1px solid #fff;
}
.photo{
    border:1px solid #fff;
    width:95px;
    height:120px;
    margin-top:10px;
    background:url('http://addyosmani.com/blog/wp-content/uploads/2011/04/painting21.gif');
    background-position:-100px -120px;
}
.photo:hover{
    padding:5px;
    height:150px;
    margin:-25px 0px 0px -5px;
    box-shadow:1px 4px 2px 0px #000;
    background-position: -450px -120px;
    /* apply transitions to all properties*/
    -moz-transition: all 0.5s ease-out;
    -o-transition: all 0.5s ease-out;
    -webkit-transition: all 0.5s ease-out;
    transition: all 0.5s ease-out;
}

Demo: http://jsfiddle.net/addyosmani/WApWn/show/

For a complete list of CSS properties that are animatable see the W3C specs on transitions here: http://dev.w3.org/csswg/css3-transitions/. I also recommend reading http://samuli.hakoniemi.net/css3-transitions-are-we-there-yet/ for a look at a wide range of different CSS3 transition demonstrations.

 

CSS Transition support

Below we can see a list of the browsers that have been confirmed as supporting CSS3 transitions.

  • Google Chrome 9+
  • Opera 10.5+
  • Firefox 4+
  • Safari 3.2+

Although this list is promising, one of the main issues with implementing CSS3 transitions now is that support for them is not what could be described as fully cross-browser compatible. As they're a relatively recent feature, users who are still on older browsers (IE6+, Firefox 3 and below) won't be able to view them as animations and this can be challenging if you're aiming for a consistent user-experience. There are however solutions to this problem and we'll be taking a look at some of them in today's post.

 

Cross-browser Transitions with jQuery

If CSS3 transitions aren't going to be available, the next obvious language to fall back on is JavaScript. Using Modernizr, a popular feature detection tool, we can detect support for CSS3 transitions quite easily. We are then able to serve up JavaScript powered animations to browsers which don't support it. 

Next, rather than handling all of the cross-browser challenges of smooth animation ourselves, we can make use of jQuery's .animate() features to provide the same animation effect if it can't be done in the current browser using CSS3. The only problem that then remains is getting .animate() to use CSS3 transitions if they're supported and easily fallback to jQuery if they're not. So what do we need to make this happen?

Our requirements:

  • Detect the vendor prefix needed for the current browser's version of transitions
  • (if they're supported) fallback to jQuery
  • if they're not attempt to support as much of the documented jQuery .animate() signature as possible (with respect to callbacks, duration, properties etc) within the scope of the post.

Note: Keep in mind that .animate() is considerably more complex than what I'll be showing you in the following example – this will support most basic animations, but really serves as a starting point for the solutions I'll be recommending later.  

JavaScript: jquery.css3transitions.js

$(function(){
/*Note: you can replace getPrefix() with Modernizr._vendorPrefix in production. I'm showing an alternative implementation here just so that you can
see what's effectively being done under the hood*/
        function getPrefix( prop ){
        var prefixes = ['Moz','Webkit','Khtml','0','ms'],
            elem     = document.createElement('div'),
            upper      = prop.charAt(0).toUpperCase() + prop.slice(1),
            pref     = "";
        for(var len = prefixes.length; len--;){
            if((prefixes[len] + upper) in elem.style){
                pref = (prefixes[len]);
            }
        }
        if(prop in elem.style){
            pref = (prop);
        }
        return '-' + pref.toLowerCase() + '-';
        }
        $.fn.extend({
            defaultAnimate: $.fn.animate,
            animate: function(props, speed, easing, callback) {
                        var options = speed && typeof speed === "object" ? jQuery.extend({}, speed) :{
                                complete: callback || !callback && easing ||
                                jQuery.isFunction( speed ) && speed,
                                duration: speed,
                                easing: callback && easing || easing && !jQuery.isFunction(easing) && easing
                            };
                          return $(this).each(function() {
                            var $this = $(this),
                                altTransition,
                                easing = (options.easing) ? easing : 'ease-in-out',
                                prefix = (getPrefix('transition'));
                                if (Modernizr.csstransitions)
                                {
                                      $this.css(prefix + 'transition', 'all ' + speed / 1000 + 's ease-in-out').css(props);
                                      setTimeout(function() {
                                        $this.css(prefix + 'transition', altTransition);
                                        if ($.isFunction(options.complete)) {
                                             options.complete();
                                        }
                                      }, speed);
                                }
                                else{
                                     $this.defaultAnimate(props, options);
                                }
                        })
                    }
        });
});

JavaScript: app.js

$(function(){
        function updateStatus(msg){
        $('.status').html(msg);
    }
        function compatTest(){
        if (Modernizr.csstransitions) {
            $('.compat span').html('yes');
        }
    }
        compatTest();
        $('.alt0').animate({width: '480px'}, 4000, function(){
                updateStatus('Animation 1 Complete');
        });
        $('.alt1').animate({width: '480px'}, 5000, function(){
                updateStatus('Animation 2 Complete');
        });
        $('.alt2').animate({width: '480px'}, 7000, function(){
                updateStatus('Animation 3 Complete');
        });
});

HTML:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>Demo 1</title>
  <link href='css/style.css' rel='stylesheet' type='text/css'>
  <link href='http://fonts.googleapis.com/css?family=Dancing+Script' rel='stylesheet' type='text/css'>
  <script type='text/javascript' src='js/jquery-1.5.1.min.js'></script>
  <script type='text/javascript' src="js/modernizr-1.7.min.js"></script>
  <script type='text/javascript' src="js/jquery.css3transitions.js"></script>
  <script type='text/javascript' src="js/app.js"></script>
</head>
<body>
  <div class="container">
    <h1>CSS3 Transitions</h1>
    <h4>with a jQuery fallback</h4>
    <div class="bar"><div class="box alt0"></div></div>
    <div class="bar"><div class="box alt1"></div></div>
    <div class="bar"><div class="box alt2"></div></div>
    <div class="notebar compat">CSS3 transitions supported? <span>no</span></div>
    <div class="notebar">Using : sample code</div>
    <div class="notebar status">Waiting for animations to complete...</div>
</div>
</body>
</html>

Demo Preview:

Demo:
http://jsfiddle.net/addyosmani/A89Sq/show

While this works fine for animating basic properties using the signature variants supported by .animate(), the routine above is in no way comprehensive for complete coverage of what it's fully capable of. If you're looking for something good enough for production use, I recommend reading on below for an introduction to two more complete implementations of transition fallbacks.

If you did however want to play around with the above could further, there are a number of areas in which it could be improved. It could include support for fx steps, fadeIn()/fadeOut(), CSS3 transitionend events, jQuery's queues and delay() as well as improve the interpretation of more complex CSS3 transitions and transforms.You could also easily add support for the shorthand animation speeds 'slow' and 'fast' through jQuery.fx.speeds.slow and jQuery.fx.speeds.fast if needed.

Additional Demos:

Adrian Sinclair, a fellow developer interested in transition fallbacks came up with this next demo which we'll be also testing out later with two other fallback solutions we'll be reviewing. Adrian's demo below uses a slightly different version of code than I presented above but it's aiming at the same general idea of transition fallback support through jQuery.

http://jsfiddle.net/addyosmani/Mxyaj/show/

 

More Robust Solutions

Rather than investing time and effort into extending the above code, I'd like to recommend two other solutions that are both more feature extensive, but also which get updated more regularly than the example I've shown you today.

jquery.transitions.js

The first solution I'd like to recommend is jQuery plugin developer Louis-Rémi Babé's jquery.transition.js. It's a robust jQuery fallback for CSS3 transitions and here's what Louis had to say about it when I spoke to him earlier:


"Unlike other transition polyfills, this plugin is not a monkey patch over jQuery animation mechanism. It is actually a patched version of effects.js (animation component),  stripped from the redundant code to make it a lightweight plugin.

The main benefit is that the full jQuery API is available. Even better, it has been designed to be compatible with cssHooks. The first compatible one is jquery.transform.js. Others will follow.

jquery.transition.js has been tested against jQuery unit-tests, and only 8 of them fail, mostly for timing issues. It benefits from the experience of writing csstransition.net to workaround implementation quirks.

In my opinion, its a good practice to test animations both with and without the plugin to see if the benefit experienced is real"

To summarize, let's now breakdown the benefits and downsides to using jquery.transitions.js:

Benefits

  • It supports the complete jQuery API, failing only 8 tests in the official jQuery test-suite
  • Due to test coverage, you're ensured a significantly more stable solution that will work with more complex animations
  • In addition to animate() it also supports show(), hide(), fadeTo(), toggle() and stop()
  • Supports jQuery queues.
  • Compatibility with jquery.transform.js

Downsides

  • Limited Opera compatibility – transitions in jquery.transitions.js work best with Mozilla and Webkit based browsers. The source claims the reason for this is the instability of Opera 11's transitions implementation. This is easily patchable however if you still want to enable CSS3 transitions rather than the fallback for Opera.
  • Transitions are disabled for special easing and step functions.

The minor downsides aside, in my experience Louis's solution is considerably more stable for heavy-duty animations than many of the other homebaked solutions out there. I definitely recommend checking it out.

jQuery transitions demos:

 

Demo 1 (Progress Bars): http://jsfiddle.net/addyosmani/XKrcX/show

Demo 2 (Box Grid): http://jsfiddle.net/addyosmani/fQhsv/show

Demo 3 (Sliding content): http://jsfiddle.net/addyosmani/55qRn/show/

 

Bonus demo:

As we've been taking a look at fairly simplistic animations so far, it's also useful to compare how well the transitions plugin handles actual animated scenes.

For this purpose, I've created a sample scene from 'All-Star Superman' which will similarly use CSS3 transitions if they're supported, but happily run using jQuery's standard .animate() if not. It's a short scene as this is just a demonstration, but you can easily see some of the benefits of maintaining the jQuery-stynax you're already used to for defining animations vs. perhaps opting for writing out all your transition statements separately.

var animScenes = animScenes || {};
animScenes = {
    city: $('#container'),
    superman : $('#superman'),
    dc : $('#dccomics'),
    lex : $('#lexluthor'),
    logo : $('#supermanlogo'),
    triggerScene1: function(){
       var sq = this;
       this.dc.animate({'opacity':'0'},3000, function(){
           sq.triggerScene2();
        });
    },
    triggerScene2 :function(){
       var sc =  this;
       this.dc.remove();
       //animate city for fake perspective
       this.city.animate({'top':'-200px'}, 5000);
       //superman flies into view
       this.superman.show().animate({'width':'436px','height':'326px'}, 3000);
       //lex flies close to catch up
       this.lex.show().animate({'marginTop':'90px', 'width':'190px','height':'190px'}, 5000, function(){
       //superman speeds away
           sc.superman.animate({'marginTop':'-400px','marginLeft':'1000px'}, 500, function(){
                //lex chases after
                sc.lex.animate({'marginTop':'-320px','marginLeft':'1000px'}, 300);
                //end scene with logo fading in
                sc.logo.show().animate({'width':'500px','height':'228px'}, 900);
            });
        });
    }
};
animScenes.triggerScene1();
	

If you're looking for an .animate() solution that provides stable, tested CSS3 transition support, I strongly recommend using Louis's plugin. You can get the sources to it over on GitHub.

 

jQuery.animate-enhanced.js

The other solution you might like to check out is Ben Barnett's jQuery.animate-enhanced.js. Similar to Louis's, his plugin analyzes the properties you're animating on and selects the most appropriate approach for use in your current browser.

Benefits

  • Supports fadeIn(), .fadeOut(), .slideUp(), .slideDown() and slideToggle().
  • Supports $.fn.stop([clearQueue, jumpToEnd, leaveTransforms])
  • Includes $.translate() method to calculate x/y transforms from CSS3 Matrix
  • Animation 'queue' support (including .delay())
  • Supports Opera out of the box

Downsides

  • Works well with simple animations but can run into issues with more complex ones
  • Occasionally has issues with animation and positioning

I first played around with Ben's plugin about five months ago and I think the only downside to it is that it occasionally has trouble animating more complex animations which is something I haven't seen jquery.transitions.js affected by as much. It's still quite a strong contender for usage and I'd recommend reading up on it more before making a decision on which plugin is right for you.

Ben's plugin is almost identical to the jQuery animate() function, but unlike Louis's comes with three additional parameters, which are optional:

  • avoidTransforms: (bool) By default his plugin converts left and top animations to the CSS3 style -webkit-transform (or equivalent) to aid hardware acceleration. This functionality can be disabled by setting this to true.
  • useTranslate3d: (bool) By default the plugin uses 2d translations due to wider browser support. Setting this to true to use translate3d instead. This is recommended for iPhone/iPad development.
  • leaveTransforms: (bool) By default if the plugin is animating a left or a top property, the translate (2d or 3d depending on setting above) CSS3 transformation will be used. To preserve other layout dependencies, once the transition is complete, these transitions are removed and converted back to the real left and top values. You can set this to true to skip this functionality.

 

jQuery Animate-Enhanced demos:

 

Demo 1 (Progress Bars): http://jsfiddle.net/addyosmani/XSLbe/show

Demo 2 (Box Grid): http://jsfiddle.net/addyosmani/kfGUc/show

Demo 3 (Sliding content): http://jsfiddle.net/addyosmani/XwJHe/show/ (breaks the layout whilst jquery.transitions.js doesn't)

 

Conditional Polyfill Loading Approach

Another approach to handling the requirement for a fallback is by using a conditional polyfil loader like yepnope (if you're interested in a tutorial on yepnope, I wrote one for .net magazine back in issue #213) . With the help of Modernizr, you could easily test for CSS3 transition support in the script loader phase and then load a pure CSS3 version of the animation using a stylesheet if transition support is detected. If not, you could then load a jQuery + fallback script version of the animation.

 

yepnope({
  test: Modernizr.csstransitions,
  yep: 'animation.css',
  nope: ['base.css', 'jquery.transition.js', 'animation.js'],
  complete: function () {
    alert('Everything has completed loading!');
  }
});

The reason I personally prefer not doing this is because it requires you writing two versions of the same animation (one in CSS, another in JS/jQuery) whilst simply defining your animation through jQuery's .animate() (and having a plugin convert to transitions for you) can be easier depending on the complexity of what you're working with. Use your best judgement with respect to how you structure your solution, but you should now have at least a general idea of at least two ways this problem could be tackled. The above works best if your animation code is not likely to be complex.

 

Conclusions & Important Notes

And that's it!. If you decide to use one of the fallbacks presented in today's post, remember to thoroughly test your animations correctly render throughout without any issues. Also remember that using transitions don't always mean smoother solutions (in particular when they're triggered from the DOM rather than through CSS). It's just as important to test your pages both with and without transition plugins to ascertain what the real perceived improvements are both on desktop and mobile devices.

I hope this comes in useful!.


72 Comments

    • I've released a few upgrades recently to support more easing algorithms and a few other fixes.

      Please add any issues (particularly those positional ones – great test by the way) on Github and I'll get to them as soon as.

    • If you're looking to install this on WordPress, your best bet is probably going to be adding in any of the jQ/JS logic into an external .js file that works alongside markup you publish via either a post or page. The CSS needed can easily go into any themes style.css / CSS files.

  1. Hey this is awesome, great post. I've released a few upgrades recently to support more easing algorithms and a few other fixes.

    Please add any issues (particularly those positional ones – great test by the way) on Github and I'll get to them as soon as.

  2. Pingback: CSS3 Animation With jQuery Fallbacks | Follia Digitale

  3. Nice, detailed post.

    I've been thinking about transitions for a while, and I can't help feeling that now the 'right' way to define them is in CSS. I can see the benefit of adding them to jQuery animate, but my gut says there is something beautiful about defining them in CSS that animate() lacks – typically less code, less logic, and the ability to easily define complex effects like animating children.

    So I have a plugin that approaches the problem from the other end [cough, cough, github.com/stephband/jquery.transitions]. It adds .addTransitionClass() and .removeTransitionClass() to the jQuery.fn namespace. These fallback to .addClass() and .removeClass() when transition support is not detected, or when fallbacks are not provided in the options.

    I realise that one of the points above is to avoid defining animations twice, and in this scheme the the fallback function will typically contain .animate() calls – but where I do complex transitions in CSS, I'll usually provide a simple alternative in JS, or none at all if it's not critical.

    You could see this as graceful degradation rather than progressive enhancement – but it's not: any transition definition is an enhancement.

    Anyway, I admire the work that's going into these plugs and will be nicking some ideas :) Cheers!

    [Oh, and before you flame me, when I say 'right' I don't mean to imply that anyone is 'doing it wrong'!]

    • Very interesting. I don't think at all that there's a 'right' or 'wrong' way to approach this problem just yet, but I will be checking out your jquery.transitions plugin. It's certainly an approach to the problem I hadn't considered.

        • Oh! I didn't think there was anything wrong with your choice of words at all :) I actually wrote up a much more detailed response to your comment initially, but intensedebate decided to swallow it up. In terms of preference to approach the problem, as I said I think that developers and designers should use whatever they feel the most comfortable with. As long as the end solution has the level of cross-browser compatibility or the experience you're happy users with specific browsers seeing, I'd say feel free to experiment!

    • I don't think there's par se, a 'right' or 'wrong' way to approach this problem just yet but defining your syntax in the way you feel most comfortable with is definitely something to consider. I believe I may have mentioned this in the post, but I feel that if your animations aren't overly complex, it may well make sense to go the extra distance to do a version in CSS3 and another in JS. Hopefully one day such workarounds to get it functional cross-browser will be a distant memory :)

  4. One nice thing about CSS3 transitions is that you can animate a very large number of elements at once and synchronously while jQuery animations are based on single elements. This was really a problem for us when we tried to animate several hundreds of divs. As solution we build a little library called jQuery Mass Animate that animates style rules instead of element styles. So if you are facing the same problem then have a look at https://github.com/medihack/massanimate

    • Your solution looks quite interesting (and again, thanks to everyone posting about transition and animation-related resources, they're definitely helpful). Do you have any perf tests or numbers on how well this might compare to the standard way of animating with jQuery? :)

  5. Pingback: Link-urile săptămânii 4-10 aprilie | Staicu Ionuţ-Bogdan

  6. Pingback: Animazioni CSS3 con fallbacks in jQuery! | sastgroup.com

  7. Pingback: Animazioni CSS3 con fallbacks in jQuery! | buonaguida.com

  8. A wee update on jquery.transitions (github.com/stephband/jquery.transitions) – automatic IE fallback support!

    I made the discovery today that IE lets you read unsupported CSS properties of nodes, through:

    // For IE6, IE7, IE8
    jQuery(node).css('transition')
    // For IE9
    (window.getComputedStyle && getComputedStyle(node, null).transition)

    This is good news. It has allowed me to write automatic animation fallbacks for CSS transitions that are defined in CSS and applied by adding and removing a class with .addTransitionClass() and .removeTransitionClass().

  9. Pingback: Useful CSS3 Tutorials From 2011 | qeqnes | Designing, jQuery, Ajax, PHP, MySQL and Templates

  10. Pingback: Amazing HTML5 and CSS3 Tutorials 2011 « arena3000

  11. Pingback: - TechnoGrate

  12. Pingback: -webkit resources for Tablet Hybrid Apps | [the]PrintLabs

  13. Pingback: 40 Excellent CSS3 Tutorials | 1 step web design

    • Depending on the type of animation you’re attempting, some ‘flash’-like effects are possible. Keep in mind cross-browser compatibility however as some of the current 3D transformations are only still possible in a limited number of browsers.

  14. Pingback: Web Design SEO Ecommerce – 2 Hour Sites» Web Design Trends in 2012

  15. Pingback: Web Design Trends in 2012 | CS5 Design

  16. Pingback: Web Design Trends in 2012 | Momoto Design

  17. Pingback: Web Design Trends in 2012 | Evolve Inc

  18. Pingback: Web Design Trends in 2012 « Cloud9 Web Designs

  19. Pingback: WDL博客视点:2012年网页设计趋势 - 逼他网

  20. Pingback: WDL博客视点:2012年网页设计趋势 | 资讯门

  21. Pingback: WDL博客视点:2012年网页设计趋势 | 69UI.com | UI前端设计

  22. Pingback: 2012年网页设计趋势 | 刘兴涛&LiuXingTao.com™

  23. Pingback: Creabite. Mordisco Creativo. – Web Design Trends in 2012

  24. Pingback: Trends for 2012 | Entice

  25. Great demo. Of all the demos I have found, this is by far the most robust. JQuery fallbacks, Modernizr… you guys are on top of your stuff. Thanks.

  26. Pingback: Wordpress Web Design Trends for 2012 - The Media Group

  27. I don’t if someone still sees this post, but i have kind of a problem with using jquery.transitions.js.

    Today i’ve tested my ass off on on my mac with all browsers, on windows with explorer, iphone, android, ipad and allthough it works everywhere it still looks like it’s not as smooth as real css transforms for some reason. when i have the same simple animations done in css.

    Has anyone else have the same ‘problem’? I feel it’s like the plugin is still using the native jquery animate instead of css transitions….

Leave a Reply

Required fields are marked *.

*