Using jQuery as a Fallback for CSS3 Transitions

Everyone is keen to get started with CSS3, and transitions are a great way to animate a hover effect or any change between CSS properties. This works great for people with modern browsers, but there are quite a few people who may miss out on the greatness.

As far as I can work out, CSS3 transitions are implemented on the following browsers:

  • Safari 4+ (introduced 2008)
  • Firefox 4+ (2009)
  • Google Chrome 7+ (introduced 2010)
  • Opera 10.50+ (introduced 2010)

I may be wrong with a couple of versions (where does google keep it’s version history?) but did you spot the glaring omission?

No single version of internet explorer, not even 9 supports transitions. I’m sure they will implement this in the future, but right now there are a lot of people who won’t get the full experience.

So what can we do about this? We could just stick with jQuery for animating, but this seems kind of like admitting defeat. What we need is some graceful degradation for browsers that don’t support CSS transitions.

Modernizr is a great script for detecting which features a browser supports, but it seems like overkill at 10kb. I took the source from GitHub and reduced it as much as possible. The resulting script is 283 bytes after compression, much better!

Uncompressed version:

function cssTransitions() {
    m = document.createElement('z');
    s = m.style;
    function test_props( p ) {
        for ( var i in p ) {
            if ( s[ p[i] ] ) {
                return true;
            }
        }
        return false;
    }
    function test_props_all( prop ) {
        d = 'Webkit Moz O ms Khtml'.split(' ');
        var u = prop.charAt(0).toUpperCase() + prop.substr(1);
        e = (prop + ' ' + d.join(u + ' ') + u).split(' ');
        return test_props( e );
    }
    return test_props_all( 'animationName' );
}

After compressing with Closure Compiler:

function cssTransitions(){m=document.createElement("z");s=m.style;return function(f){d="Webkit Moz O ms Khtml".split(" ");var a=f.charAt(0).toUpperCase()+f.substr(1);e=(f+" "+d.join(a+" ")+a).split(" ");a:{for(var b in e)if(s[e[b]]){f=true;break a}f=false}return f}("animationName")}

This function creates an element in the dom and tries to apply the css “animationName” to it. We can use it like this:

if ( !cssTransitions() ) {
    // Browser doesn't support CSS Transitions
    // Let's fall back to jQuery instead
}

So, if you had the following CSS transition:

.animate a:link, .animate a:visited {
    opacity: 0.75;
    -moz-transition: opacity 0.3s ease-out;
    -o-transition: opacity 0.3s ease-out;
    -webkit-transition: opacity 0.3s ease-out;
    transition: opacity 0.3s ease-out;  
}
.animate a:hover, .animate a:focus {
    opacity: 1; 
}

Then you could fall back with:

if ( !cssTransitions() ) {
    $('.animate a').hover(function() {
        $(this).css({opacity:0.75}).fadeTo(300, 0.5);
    }, function() {
        $(this).css({opacity:1}).fadeTo(300, 0.75);
    });
}

We have to set the CSS before transitioning it in order to prevent the hover state from appearing before we transition it.

This article seems very code heavy, but I can’t leave you without my last piece of the puzzle; color animation

jQuery UI comes with color animation, but again we don’t need all the code. I got the source from GitHub and grabbed just what I needed, so if you want to animate color, all you’ve got to include is:

$.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(f,a){$.fx.step[a]=function(b){if(!b.colorInit){b.start=getColor(b.elem,a);b.end=getRGB(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],
10),255),0)+")"}});
function getRGB(f){var a;if(f&&f.constructor==Array&&f.length==3)return f;if(a=/rgb(s*([0-9]{1,3})s*,s*([0-9]{1,3})s*,s*([0-9]{1,3})s*)/.exec(f))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb(s*([0-9]+(?:.[0-9]+)?)%s*,s*([0-9]+(?:.[0-9]+)?)%s*,s*([0-9]+(?:.[0-9]+)?)%s*)/.exec(f))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f))return[parseInt(a[1],16),parseInt(a[2],
16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba(0, 0, 0, 0)/.exec(f))return colors.transparent}function getColor(f,a){var b;do{b=$.curCSS(f,a);if(b!=""&&b!="transparent"||$.nodeName(f,"body"))break;a="backgroundColor"}while(f=f.parentNode);return getRGB(b)}

Slightly heavier than the Modernizr code snippet, but still not bad at 1kb.

So that’s it. Hope you find some use for this. Now we can use CSS3 and sleep easy.


Notice: Array to string conversion in /var/www/joshemerson.co.uk/public_html/site/plugins/tags.php on line 22

Fatal error: Uncaught Error: Function name must be a string in /var/www/joshemerson.co.uk/public_html/site/plugins/tags.php:22 Stack trace: #0 /var/www/joshemerson.co.uk/public_html/site/templates/post.php(12): tags(Object(page)) #1 /var/www/joshemerson.co.uk/public_html/kirby/lib/template.php(36): require('/var/www/joshem...') #2 /var/www/joshemerson.co.uk/public_html/kirby/lib/template.php(25): tpl::loadFile('/var/www/joshem...', Array, true) #3 /var/www/joshemerson.co.uk/public_html/kirby/lib/site.php(203): tpl::load('post', Array, true) #4 /var/www/joshemerson.co.uk/public_html/kirby/system.php(65): site->load() #5 /var/www/joshemerson.co.uk/public_html/index.php(71): require_once('/var/www/joshem...') #6 {main} thrown in /var/www/joshemerson.co.uk/public_html/site/plugins/tags.php on line 22