Many a post on javascript forums are still on the subject of image pre-loading via vanilla javascript. With so many results on google on a simple pre-loading images via javascript search, it’s surprising how very few scripts do a decent job. To understand why, we need to look at the possible pitfalls here but also to examine when and how image pre-loading can be enhancing the user experience. In other words do you really need it?
There are two scenarios here. Case 1: you just want to load all your images prior to displaying the rest of your page. Whether that’s because you like it this way or you need to get the browser to ‘know’ your images widths and heights first, it does not matter. The arguments for ‘speeding up’ page loads are a bit moot now, with modern connections and modern browsers there are no great benefits to preloading. Either way, you need to arrange your code in a way that allows you to trigger some event or function when the loading is complete in order to continue displaying your site.
And then, there’s case 2: precognitive image loading / cache priming, which is the more interesting use. This is a relatively new trend that involves examining the user’s browsing pattern and anticipating what they do next. For instance, if they are browsing product listings as a result of a search with 100 results (20 per page), it is almost safe to assume they may want to go to page 2 or 3. It is also safe to assume the average user would take a while to finish appraising their results, scroll down and locate the ‘next’ link. This idle slot is where you can put their connection to better use by priming the browser cache with the product thumbnails of the next results page. In this instance, you don’t really care for any onload / oncomplete events, the benefits of the cache will be reaped on the next page instead.
Now, we’ve established why you may want to preload images, let’s focus on the how instead.
There are some considerations we need to be aware of here:
- It needs to be threaded and not sequential (to use the browser’s maximum threads)
- It needs to raise the browser’s onload event so we ‘tick off’ our image from our images array and / or save the new image object into a new array for later insertion
- There are differences and quirks in the way the onload event works between browsers that we need to accommodate for
- We need to clean up resources and memory, or at least not leak
Now, 2 of these are more important than the rest. First of all, it’s important to understand that you need to assign the onload event BEFORE you set the src property of the image object:
// wrong:
var image = new Image();
image.src = 'image.jpg';
image.onload = function() { // evil, won't work if image already cached, it won't trigger
...
};
// right:
var image = new Image();
image.onload = function() { // always fires the event.
...
};
image.src = 'image.jpg';
And second, in Internet Explorer, the onload event fires ALL THE TIME for animated gifs. That’s right, on each loop it fires an ‘onload’ for you. Which is why you need to make sure you release your image objects – or should you need to store them as objects into a new array – remove all onload-based events attached to them.
Anyway, without further ado, the code. I hope it makes sense – you can see it working here.
// assign images as global
var imagesArray = [
"8-0.gif",
"article-bg.gif",
"article-page-bg.gif",
"big-bg.gif"
]; // etc etc, as many image elements as you need to cache.
window.onload = function() {
// local scope. strictly speaking, images loading does not need to wait for onload, so you can change this.
// good to keep it under a namespace / anon closure if you don't use the window.onload.
var loadedImages = [], reportProgress = function(where) {
// used to show how many images loaded thus far and / or trigger some event you can use when done.
// optional to show onscreen..., 'where' should be an object referencing an element in the DOM
// to do it silently, just remove the output bits below.
var output = "loaded ";
output += loadedImages.length;
output += " of ";
output += imagesArray.length;
output += " images.";
where.innerHTML = output;
// this bit will fire when all images done:
if (imagesArray.length == loadedImages.length) {
// total images onComplete. done. rewrite as you deem fit - call your main site load function from here
// keep in mind that if 1 of your images is a 404, this function may not fire, you
// may want to assign onerror and onabort events
// you can loop through them (if you use the objects, then output loadedImages[x].src or inject them.
for (x in loadedImages) {
where.innerHTML += "" + loadedImages[x]; // comment this out - debug.
}
}
}, loadImage = function(imageSrc, reportDiv) {
// actual function that loads image into DOM
var image = new Image(); // local scope, new object created per instance.
image.onload = function() {
if (!image) return;
loadedImages.push(image.src); // record this image path as loaded in global scope
// or... store the objects themselves so you can inject them:
// loadedImages.push(this);
// sample report of progress
reportProgress(reportDiv, image.src);
// remove event and self to prevent IE on animated gif loops and clear up garbage.
image = image.onload = image.onabort = image.onerror = null;
};
image.src = imageSrc;
}, total = imagesArray.length, loadedProgress = document.getElementById("mydiv"); // more local variables
// loop through the images and load.
while(total-- || loadedImages.length == imagesArray.length) {
loadImage("http://fragged.org/images/" + imagesArray[total], loadedProgress, total);
}
}; // end window.onload
I really do hope this helps somebody.