Preloading Images with jQuery and JavaScript

Posted: December 1, 2009 In

Creating effects on websites using jQuery and other JavaScript has really grown from flashy extras to full on applications in the browser. Part of slick applications is images. They might be part of the content being loaded or something like an icon in an application. But, for those images being added after the page is loaded there can be a time gap while that image is downloaded. To avoid those time delays the images can be preloaded in the background. Lets take a look at how to do that.

A jQuery Script

Here is a simple jQuery script to preload the images.

(function($) {
  var cache = [];
  // Arguments are image paths relative to the current page.
  $.preLoadImages = function() {
    var args_len = arguments.length;
    for (var i = args_len; i--;) {
      var cacheImage = document.createElement('img');
      cacheImage.src = arguments[i];
      cache.push(cacheImage);
    }
  }
})(jQuery)

You'll notice that this isn't anything special to jQuery. You can change the name from $.preLoadImages() to some other function name and use this outside of jQuery.

To use the preloading make a call like:

jQuery.preLoadImages("image1.gif", "/path/to/image2.png");

Breaking Down The Script

Let's break down this snippet of code to see what's happening.

(function($) {
  ...
})(jQuery)

We are wrapping the code in an anonymous function and passing jQuery in with a $ alias. This is described in the jQuery plugin authoring documentation. The reason for doing this is to avoid conflicts with other scripts that may be in the page.

var cache = [];

Inside the anonymous function we have a variable to cache the images we load. The images may not be preloaded if they are just loaded locally inside $.preLoadImages(). This is due to some broswers garbage collection cleaning up local variables.

$.preLoadImages = function() {

The reason this is attached to jQuery is to avoid cluttering the global namespace. If you aren't using jQuery in the page or have an application global object it may be better to attach it there.

var args_len = arguments.length;

We are going to use the length (number) of arguments in a loop. If we use arguments.length in the loop it will calculate the number of arguments on each pass through the loop. We only need to do this once so we store the length in a variable.

for (var i = args_len; i--;) {
  ...
}

Here we loop through all the arguments and act on each one. We are not worried about the order the images are preloaded so we are loading them from last to first. Loops that count down are faster than loops that count up.

var cacheImage = document.createElement('img');
cacheImage.src = arguments[i];
cache.push(cacheImage);

We create an image as a DOM element in the browser. In some other scripts jQuery('<img>') is used to create the image. This will work but it much slower than using the DOM. Once the image is created set the src to the path of the image and attach it to the cache array.

Special thanks to Nathan Smith for reviewing and tweaking the code.

Reader Comments

WOW! I looked for this time ago! This is so useful! Thanks!!

How will you know if they've completed loading?

This is just a simple preloader. You don't know when an image has completed loading or its status along the way. The function could be modified to take advantage of the onload event for the image to notify you when the image is loaded.

I have done an autoloader with callbacks..
it preloads images from an array of urls and/or from CSS as well...

more info here: http://binarykitten.me.uk/2009/02/11/jquery-image-preloader-plus-callbacks/

That is a good preloader! I messed around a lot awhile ago with a good way to have image loading and displaying behave nicely across browsers. I came up with a jQuery plugin:

jQuery.fn.onImagesLoaded = function(_cb) { 
  return this.each(function() {
 
    var $imgs = (this.tagName.toLowerCase()==='img')?$(this):$('img',this),
        _cont = this,
            i = 0,
    _done=function() {
      if( typeof _cb === 'function' ) _cb(_cont);
    };
 
    if( $imgs.length ) {
      $imgs.each(function() {
        var _img = this,
        _checki=function(e) {
          if((_img.complete) || (_img.readyState=='complete'&&e.type=='readystatechange') )
          {
            if( ++i===$imgs.length ) _done();
          }
          else if( _img.readyState === undefined ) // dont for IE
          {
            $(_img).attr('src',$(_img).attr('src')); // re-fire load event
          }
        }; // _checki \\
 
        $(_img).bind('load readystatechange', function(e){_checki(e);});
        _checki({type:'readystatechange'}); // bind to 'load' event...
      });
    } else _done();
  });
};

and can be used like this...
...say this is the html in the page:

 
<body>
<div id="pix">
  <img src="nodes.jpg" />
  <img src="BuseyPRed.jpg" />
  <img src="wiistick.jpg" />
</div>
 
<img id="single" src="phelps_516_0102_25518a.large.jpg" />
 
</body>

...this would be the jQuery using the above plugin to display images when they are ready,
note it can be a single image or an 'image container' so to speak...

jQuery(function($){ // on page ready
 
  $('div#pix').onImagesLoaded(function(_this){
    $(_this).fadeIn(10000);
  });
 
  $('img#single').onImagesLoaded(function(_img){
    $(_img).fadeIn(3000);
  });
 
}); // end on ready

A few notes:
this could be greatly improved/extrapolated to be easier to use.
1st, this little example assumes that all images OR image containers
have style 'display:none' to start with...(the script could be tweaked to do this automatically)
i.e., the example described requires this css somewhere:

  div#pix { display:none; }
  img#single { display:none; }

2nd, the onImagesLoaded() function takes a callback as its argument, and the element that the plugin was called on is passed back to this callback function as the only param. I'm not a jquery plugin expert by any means, so I could have written it slightly wrong, and I am sure there is a lot of room for improvement. it works in most of the browsers I've tested (IE7 im sure, 6 I can't remember, and all the other nice ones FF/Safari/Op/Chrome). The plugin could also be tweaked to not need a callback at all but instead just accept params like 'fade' or 'slide' or similar, and that logic could be done right inside the plugin. I just haven't needed it to be that simplistic for my uses thus far...

Anyway, if anyone finds it useful or can improve greatly on it, rad. do it. I've been using it for awhile now...

Thanks. I updated the comment to add source highlighting.

ah thanks! I missed the  tag, my bad

oh and sorry, I don't have a blog or anything to post that on, hope that is ok :)

First, I don't understand why you have the cache; once a browser has fetched a resource, e.g. an image, it's cached for the duration of the page.

Second, I don't believe you need to create a DOM element; images are objects in JavaScript, and you can simply do:

for (var i = args_len; i--;) {
new Image()).src = arguments[i];
}

Great questions. Let me address each of your points.

The first one about the cache is actually something that has bitten me in the past. If the image is attached to the variable in the function we can run into an issue with aggressive garbage collection systems in a browser. If the preLoadImage call is complete before the image is downloaded an aggressive garbage collection system will clean up the variable and the image will not be preloaded.

Typically, you don't see this behavior on local testing servers either. When you add the delay of connecting to a remote server is when you will see this happen.

As for the second, you could use the Image object in the way you suggest. That will work as well. My understanding is that the recommended way is to create a DOM object instead.

I did a comparison between using an image DOM element and using the Image object in JavaScript from a performance standpoint. I'd still use the DOM element. You can see the comparison at http://engineeredweb.com/blog/09/12/performance-comparison-documentcreat...

It ate a parenthesis, but that should be: (new Image()).src = arguments[i];

You are wrong

Is there any way to get to the variables that hold the image elements directly from the cache? I'd like to use the preload code you posted above but still have a reference to the image element variable as:

var loading = document.createElement('img'';
loading.src = 'loading.gif';
var dialog = $('<div></div>').append(loading);

where the "loading.gif" image would be preloaded and I'd be able to access it directly from the cache.

In JavaScript the array keys are numbers. You need to use objects to add useful names. Using a / in a property name will give you a syntax error. So, you'd need to create a hash for a useful property name. This complexity is going to turn out to be slow.

You are better off preloading the image with the script on this post. Then later creating a new img element and attaching the image there. Since the browser has already cached the image you are only duplicating the DOM element which is cheaper than the complexity needed to create a cache you can retrieve the element from.

That is, unless you are dealing with thousands of images. If you are I'd considering a different strategy.

Hello,

Is it possible to make an animation with an opacity from 0 to 100 after the image is charged ?

When the image is charged:
I would like a black background and the image appears quietly.

I'm french and i dont speak good english.

Thx

This script loads images without them appearing in the page. Creating what you want is possible but different.

Different because it's juste une animation after the preloading ?
Or because it's an another kind of code ?

sog

just consider whether you are better of using CSS Sprites. Only on image to load and no javascript at all - pure CSS.

It is a little less flexible though.

Thanks for pointing this out Geoff.

If the images make sense to load with CSS sprites that is the better way. You will have less http traffic meaning it will be faster. The bulk of the time taken to download an image is in the transaction with the hosting server.

This JavaScript method should only be used for cases where CSS sprites don't make sense.

Here is some related code whre I preload everything in the .over class

http://eduadvocates.geoffhankerson.com/sites/default/themes/eduadvocates...