jQuery fadeIn / fadeOut vs. IE ClearType Rendering

Fix your janky text already

Published on Thursday, January 21, 2010

jQuery makes fading html elements trivial, and ever day I see JavaScript fade in/out effects used all over the web.  I've used this UI trick on a few of the sites I've been working with lately and like everyone, I've experienced the frustrating jagged text issue in IE.  You know what I'm talking about if you use Facebook and have posted a comment on someone's wall post.  After you make the comment it will show up inline in typical AJAX fashion and its font will look like it was rendered on a Commodore 64.  There are a few posts like this one that address the issue with jQuery and CSS modifications, however I've not had much luck using this method and it's not very flexible.  Fortunately there is an alternative commonly used by flash developers that I've found to be easier to implement, more reliable and most importantly works seamlessly across all modern browsers.

Let's start with some content that we want to fade in and out.  In this case we'll make a simple widget that displays a series of rotating banners to your site visitors.

First, we'll create two container div elements to hold our banners.

<div id="outerContainer" style="background:#F00;height:175px;width:215px;">
    <div id="bannerContainer" style="position:relative;"></div>
</div>

You'll notice that I've set the bannerContainer position to "relative" and provided a fixed size (height and width) to the outerContainer div.  Both will be very important later on.

Next, lets add the content that we want to fade in and out.  In this case, div's that will hold our banners.

<div id="outerContainer" style="background: #F00;height:175px;width:215px;">
    <div id="bannerContainer" style="position:relative;">
        <div id="banner1" style="display:none;">
            <p>Hey, click here and buy our stuff!</p>
        </div>
        <div id="banner2" style="display:none;">
            <p>Clicky Clicky!</p>
        </div>
        <div id="banner3" style="display:none;">
            <p>Clicking here is good for you!</p>
        </div>
    </div>
</div>

Here I've set the banner div style display property to "none" to give a starting point where all the banners are hidden; we'll dynamically pick the first banner to display with a bit of jQuery code.

Finally, we're going to add the thing that will make all of this work.  I call it, "the hat".

<div id="outerContainer" style="background: #F00;height:175px;width:215px;">
    <div id="bannerContainer" style="position:relative;">
        <div id="banner1" style="display:none;">
            <p>Hey, click here and buy our stuff!</p>
        </div>
        <div id="banner2" style="display:none;">
            <p>Clicky Clicky!</p>
        </div>
        <div id="banner3" style="display:none;">
            <p>Clicking here is good for you!</p>
        </div>
        <div id="bannerHat" style="position:absolute;background:#00F;top:0;left:0;height:175px;width:215px;display:none;"></div>
    </div>
</div>

bannerHat is a child of bannerContainer who's style is set to position: absolute, top: 0 and left: 0, and display: none.  Additionally the bannerHat has the same display style (size, background, etc) as outerContainer.  This setup allows the bannerHat to be used to fade the banners without encountering the IE text fading issues.

The JavaScript that makes this work is very straight forward, but is a bit "backward" compared to the process of fading an item without the hat method.  The process looks like this (pseudo code):

  1. (First time only)  jQuery.Show() one of the banners, it doesn't matter which one.
  2. After a period of time jQuery.fadeIn() the bannerHat div.  This will hide the banner with a nice fading out effect.
  3. jQuery.Hide() the currently "visible" but hidden div.
  4. jQuery.Show() one of the other banners.
  5. jQuery.fadeOut() the bannerHat div.  This will show the banner with a nice fading in effect.
  6. Wash, rinse, repeat.

So, let's look at the JavaScript that will give us a random, rotating banner widget.

$(document).ready(function() {
  var options = {
    startIndex: 0,
    fadeInDuration: 1000,
    fadeOutDuration: 1000,
    rotationInterval: 10000,
    startDelay: 0
  };

  var rotator = function(containerSelector, hatSelector, options) {
    var children = $(containerSelector).children();
    var startIndex  = options.startIndex;
    var totalItems = children.length - 1; // this is so that we exclude the hat (the -1 part)
      
    $(children[startIndex]).show();
    setTimeout(function() {
      setInterval(function() {
        $(hatSelector).fadeIn(options.fadeOutDuration, function() {
          $(children[startIndex]).hide(function() {
            // determine the actual start index by adding one, then taking the modulus
            // this will give us the correct start index regardless of the number
            // of items we have to work with.
            startIndex  = (startIndex  + 1) % totalItems;
            $(children[startIndex]).show(function() {
              $(hatSelector).fadeOut(options.fadeInDuration);
            });
          });
        });
      }, options.rotationInterval);
    }, options.startDelay);
  };  
  // randomly pick a start index and then initiate the rotation
  options.startIndex = Math.floor(Math.random() * 3);
  rotator('#bannerContainer', '#bannerHat', options);
});

The JavaScript is pretty self-explanatory and shows how to use the hat to fade the banners in and out without directly fading the banners themselves.  This circumvents the issue with cleartype fonts when fading text in IE, works great in all the major browsers, and should be easy to adapt to any situation where you need to fade textual elements with JavaScript.

So now there shouldn't be any excuses for terrible fading in IE (yeah, I'm lookin at you Facebook...).