Determining when an image has loaded

How to solve a common problem with dynamic image loading

If you’ve ever built an animated image gallery, you might have come across this problem before. Here’s a simple image gallery that fades out, updates the image, and fades in. It’s a simple, but naïve implementation:

$('img#bad').fadeOut('normal', function() {

    $(this).attr('src',
      images[Math.floor(Math.random() * images.length)]);

    $('img#bad').fadeIn();
});

As you’ll see — if you’re on a fairly slow connection or eagle-eyed —  there’s an obvious problem:

The problem is that if you change an image’s src attribute, that image will then be fetched over HTTP, unless it’s already in your browser’s cache. In the fade in/out method used above, fadeIn() starts as soon as the image’s src attribute has been set, so the old image sometimes ‘hangs around’ during part of the ‘fade-in’ stage. Sometimes, images can be pre-loaded, but what if you’re loading images from a constantly updating feed?

Time to check whether the image has actually been loaded or not. It would be ideal if we could ‘chain’ the actions, just like the jQuery animations (e.g. fadeOut) do themselves, by passing a callback function to only execute when the image has actually been loaded. The process would then become:

  1. Fade the image out
  2. When it’s completely faded out, load the next image
  3. When the next image has loaded, fade it in

And we could use that like so:

$('img#good').fadeOut('normal', function() {
    load_image($(this),
      images[Math.floor(Math.random() * images.length)],
      function() {
        $('img#good').fadeIn();
    });
});

Our load_image function then has the responsibility of loading the given image, and executing the provided callback function once it’s actually loaded. Since images are inline replaced elements, their dimensions are only available once the image itself has been downloaded. This gives us a great ‘hook’ onto which we can drive the ‘has image loaded’ logic; if the element has a width, the image must have loaded.

The one thing we need to be careful about is to set the image’s src to nothing before loading the new image. This ensures we can check the width change from ‘nothing’ to ‘something’.

Here’s the full function:

function load_image($img, src, callback)
{
    $img.attr('src', '');
    $img.attr('src', src);

    if (!callback) return;

    var width = 0;

    (function() {
        width = $img.width();

        if (width)
            callback();
        else
            setTimeout(arguments.callee, 1);
    })();
}

Note that I’m using setTimeout within an anonymous function to continually poll for the width, a technique explained in Paul Irish’s excellent 10 Things I Learned from the jQuery Source.

Here’s the final result which should look a lot smoother:

Browser notes

In Safari, under my testing, there appears to be less of a problem. I have no idea why, maybe I’m just imagining it, but if you’re using Safari, you’ll just have to trust me: in Firefox and IE, this is definitely a problem!

Of course, there’s a problem with IE; there had to be, didn’t there? Setting an image’s src to nothing in IE causes the ‘broken image’ icon to display (in IE7, at least). It’s only very brief, but it’s as bad as the original problem. I haven’t got around to fixing this yet, but one solution would be to load a pre-loaded 1x1 image instead of setting src to nothing. The width check would then become if (width > 1).


Tweet

Comments

Tue 24 Jan 2012 20:30

Emanprinting

Emanprinting said:

you might have come across this issue before. Here is a easy graphic collection that ends out, up-dates the graphic, and ends in.<a rel="dofollow" rel="nofollow" href="http://www.emanprinting.com">Emanprinting</a>

Leave a comment