Preloading Images With JavaScript

Anne Hathaway
Anne Hathaway photo by Brian Smith
In my recent travels through old blog posts, I’ve noticed a number of occasions where I’ve discussed how to preload images.

Almost all those examples are stupid or just plain wrong. For that, you have my apologies, and I aim to rectify those mistakes with this post.

First, why would we want to preload an image? Simply put, we intend to show it later on our Web page — either as a result of a mouseover, or a click, or some other sort of Document Object Model (DOM) event.

For example, maybe we want to mouseover a series of thumbnails, and show a larger version of that image in the same place.

Rather than making the end user wait for a new image to load as a result of doing something on a Web page, it makes sense to load the image we intend to show in advance, so it will display almost instantaneously as a result of an event.

I’ll first show why two of my previous methods for preloading images are wrong or dumb, then describe two correct ways to preload images: via basic JavaScript and via jQuery.

The lovely Anne Hathaway will be our model.

The Wrong Way: A Single img Tag And JavaScript src Loop

I previously favored preloading images by applying a single HTML img tag on a page, one without a src attribute and also hidden via CSS:

<img id="hiddenImg" src="" alt="" style="display: none;" />

Then, I would create a preloadImages() function, which took as its arguments the paths to the images I wanted to preload. I would then loop through those arguments, setting the src attribute for hiddenImg to be the paths passed to the function:


function preloadImages() {
	var img = document.getElementById('hiddenImage');
	var i;
	
	for(i = 0; i < arguments.length; i++) {
		img.src = arguments[i];
	}
}

window.onload = function() {
	preloadImages('i/d01.jpg', 'i/d02.jpg', 'i/d03.jpg', 'i/d04.jpg', 'i/d05.jpg');
}

Unfortunately for me, this simply doesn’t work. And the reason why it doesn’t work is because, if I pass more than one image to the preloadImages() function, they simply don’t have enough time to load before the next image is called.

In short, the only image that actually preloads under this methodology is the last one in the arguments list. And that pretty much makes this approach useless.

You can see what I am talking about here: http://demo.dougv.com/js_preload_images/wrong.htm

The Dumb Way: Using HTML And CSS

Better in the technical sense, but just as bad in the practical sense, is using HTML and CSS alone to preload images.

In this case, we simply add, to the end of the page, a bunch of img tags that bring in the pictures we intend to display via JavaScript, based on a DOM event. We either place them inside a hidden div or assign those images to a CSS class that sets them to display: none.

<div id="hiddenImages" style="display: none;">
	<img src="i/a01.jpg" alt="" />
	<img src="i/a02.jpg" alt="" />
	<img src="i/a03.jpg" alt="" />
	<img src="i/a04.jpg" alt="" />
	<img src="i/a05.jpg" alt="" />
</div>

You can see an example here: http://demo.dougv.com/js_preload_images/index.htm

While this is better than the wrong way, because at least all the images manage to get preloaded, this is dumb.

Suppose our client has disabled JavaScript. This approach doesn’t care; the images for our JavaScript-driven effects will still load in the background.

In other words, we encumber all the overhead of bringing in effect images for an effect that will not be seen. That’s wasteful. Maybe it’s no big deal, given that the average user has plenty of computing power and bandwidth to spare, but it’s inelegant. And by now, you know that little puts a burr in my butt more than wasteful code.

Example 1: Using JavaScript’s appendChild() Method

The proper way to preload images with JavsScript (and this time, I mean it) is to:

  • create an HTML div that is hidden;
  • dynamically create an img element for each image we want to preload;
  • assign each of those dynamically created elements a src attribute that is a path to an image we want to preload; then
  • append that dynamic img element to the hidden div.

So first, the HTML:

<div id="hiddenImages" style="display: none;"></div>

And next, the JavaScript function:

function preloadImages() {
	var hiddenDiv = document.getElementById('hiddenImages');
	var i;
	var img;
	
	for(i = 0; i < arguments.length; i++) {
		img = document.createElement('img');
		img.src = arguments[i];
		img.alt = '';
		hiddenDiv.appendChild(img);
	}
}

To invoke this, we simply call the function:

preloadImages('i/b01.jpg', 'i/b02.jpg', 'i/b03.jpg', 'i/b04.jpg', 'i/b05.jpg');

Witness this in action: http://demo.dougv.com/js_preload_images/example1.htm

I prefer to put the preloadImages() function in the head of the page, and call it just before the closing body tag.

That way, the page gets a chance to load all its elements, before we start calling the images that we intend to show later. In other words, the page gets a chance to at least start loading the images it’s supposed to be displaying at first glance, before it starts loading the images we’ll show based on clicks / mouseovers / whatever.

Example 2: Using jQuery And document.ready

Of course, no discussion about JavaScript these days is complete without showing a jQuery version. So here’s mine.

In our case, we can use the same basic methodology as in Example 1. However, there are a few changes:

  • Because jQuery has the .hide() method built-in, we’ll just hide each image as we create it. That means the div into which we are adding these images need not be hidden at all.
  • We need to call $(document).ready() sometime after we create the hiddenImages div. Otherwise, jQuery doesn’t see where it’s supposed to add the images it’s creating, and thus silently fails. So, for simplicity’s sake, I am adding both the preloadImages() function and my call to $(document).ready() just before the closing body tag.
  • We can also dynamically bind the hidden images div at the same time we bind the images. That is, we can make every element needed to preload images preconditioned on someone having JavaScript enabled. (Hat tip to Ralph for this point.)

Here’s the JavaScript we place, just before the closing body tag, to preload:

function preloadImages() {
	$('<div/>', { id: 'hiddenImages' }).appendTo('body');
	for(var i = 0; i < arguments.length; i++) {
		$('<img />').attr({ src: arguments[i], alt: '' }).appendTo('#hiddenImages').hide();
	}
}

$(document).ready(preloadImages('i/c01.jpg', 'i/c02.jpg', 'i/c03.jpg', 'i/c04.jpg', 'i/c05.jpg'));

Working example: http://demo.dougv.com/js_preload_images/example2.htm

It’s worth noting that there are a number of image effect examples and plugins at the jQuery tutorial site.

So, whether you prefer to go the old-school JavaScript route, or to get newfangled with jQuery, the proper way to preload an image for DOM-event-based display is to create image elements via JavaScript, then add those elements to some other element on the page — either an element that’s already hidden, or by hiding the images as they are created.

The code is on the demo pages, so just take it from there.

I distribute code under the GNU GPL. See Copyright & Attribution for details.

All links in this post on delicious: http://delicious.com/dougvdotcom/preloading-images-with-javascript

4 Comments

  1. Nice method of preloading images. I didn’t see this one before.
    We could also append ‘the ‘hiddenImages’ div to the DOM with JS/jQuery, ain’t we?
    Oh well… I will try that myself.

  2. @Ralph: It would be 100 percent thorough and efficient to append the hidden images div to the body tag.

    function preloadImages() {
    	$('<div/>', { id: 'hiddenImages' }).appendTo('body');
    	for(var i = 0; i < arguments.length; i++) {
    		$('<img />').attr({ src: arguments[i], alt: '' }).appendTo('#hiddenImages').hide();
    	}
    }
    

    Post updated to use this code. Thanks!

Leave a Reply

  • Check out the Commenting Guidelines before commenting, please!
  • Want to share code? Please put it into a GitHub Gist, CodePen or pastebin and link to that in your comment.
  • Just have a line or two of markup? Wrap them in an appropriate SyntaxHighlighter Evolved shortcode for your programming language, please!