Browse DevX
Sign up for e-mail newsletters from DevX


Develop a Reusable Image Cache in JavaScript

The asynchronous download ability built into today's browser DOMs is a great way of getting image-heavy pages to build faster, but it doesn't mesh well with JavaScript's single-threading execution model. Use our image cache API to build scripts that work hand-in-hand with asynchronous objects and their events.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

cripting a browser's Document Object Model may be easy these days, but while the DOM is highly functional in some areas, it remains distinctly inflexible in others. Asynchronous operations like image loading have always been a gray area, with plenty of useful events exposed by the DOM, but little advice on how to utilize them to full effect. Knowing when a block of images has loaded is often critical to achieving the page layout, functionality, and interactivity you require. In this article I'll explain how I developed a few simple classes to do just that, thereby making image-loading headaches a thing of the past.

Open up any Web site authored with Macromedia's Dreamweaver, and chances are you'll see a bit of code at the top of the page that looks like this:

function MM_preloadImages() { //v3.0 var d=document; if(d.images) { if(!d.MM_p) d.MM_p=new Array(); var i,j=d.MM_p.length,a=MM_preloadImages.arguments; for(i=0; I<a.length; i++) { if (a[i].indexOf("#")!=0) { d.MM_p[j]=new Image; d.MM_p[j++].src=a[i]; } } } }

Actually, it probably won't look quite as structured, since the whole lot's compacted into five lines, but you get the general idea. A little later you may well see an onload handler that looks something like this:

<body onLoad="MM_preloadImages('icon1.gif', 'icon2.gif', 'icon3.gif')">

The purpose of this code is to "preload" a set of images—typically onmouseover and onmouseout rollovers. If the images weren't preloaded, you would notice a perceptible delay the first time you moved the mouse over a rollover image before the rollover image would display, as the browser would have to go off and fetch it from the server then and there. In fact, "preloading" is a bit of a misnomer, as code like this makes no guarantees that it will have finished loading all the rollovers before you need to access them; it simply kicks off loading them when the document itself is ready.

With a slow connection, an unresponsive server, or graphics with a large footprint, your mouse might get there before the highlights do. For most purposes this is an acceptable risk. However, it does bring up a couple of important issues, namely: How can you determine if and when an image or set of images has been downloaded; and how should you structure the workflow of any scripts that need to manipulate them? To answer these questions, we need first to take a closer look at how the browser's DOM handles the interaction between image loading and script execution. Note that, while this article's code is specific to Internet Explorer, the principles behind the loading pipeline and its threading issues are applicable to any browser.

Images, the Loading Pipeline, and the DOM
When your Web browser opens an HTML page, it parses the document for IMG tags and starts to load them asynchronously while simultaneously running any JavaScript it's been asked to execute. Loading images isn't the only thing that happens asynchronously; XML data sources, ActiveX Controls, and even the document itself pass through a series of compositional stages commencing with uninitialized and culminating in complete—meaning that everything's been loaded, wired up, and is ready to go. Each time an object moves on to the next stage, it fires an onreadystatechange event, and at any time you can also check its readyState property to determine which loading stage it's currently at.

There's a good argument for why things are the way they are. Why bother to download images one by one and put your browser at the mercy of a single, slow server, when you can download them all at once? And because images take up so much bandwidth, why not do something productive while you wait for them—like get on with processing the rest of the page?

Unfortunately, the DOM's asynchronous loading model doesn't collaborate too well with JavaScript's single-threaded execution model, meaning that it can be difficult to determine if and when all the images on the page have finished loading.

This is important because it can have significant repercussions for the workflow of any scripts on the page. For example, suppose you wanted to write a slideshow that loaded source images of varying sizes and displayed them in a box on the screen. In order to preserve the aspect ratio of each image, you would obviously need to know its dimensions before you attempted to display it. But until an image has at least partially loaded, the DOM doesn't know much about it, and this means you can't retrieve a meaningful width or height for it. Given that you can kick off loading an image with code like:

function LoadImages() { var im = new Image(); im.src = "_images/A1.jpg"; }

you might consider preloading the slideshow's image set with something like this:

// Load some images LoadImages(); // When the images have loaded, do something else DoSomethingElse();

Unfortunately, you can't. Because the DOM loads asynchronously, DoSomethingElse() in the code above would get called before LoadImages() had completed. "Aha!" you might think. "I can solve that problem by getting each image to set a flag when it's finished and then poll for that flag," using code like:

// Load the first image var im = new Image(); im.src = "_images/A1.jpg"; // When it's loaded, do something else while (im.readyState != "complete") { // Just loop round, doing nothing } // Now do something else DoSomethingElse();

This won't work either, but for a different reason. Once execution enters the polling loop, it never leaves it, and consequently the thread has no spare cycles with which to load the very image it's waiting for. im.readyState will never complete; in fact, since the browser runs as a priority process, your entire system could well lock up for some time.

I have seen many other ingenious attempts to get around JavaScript's threading problems; some, involving timers, may work partially. But all this cleverness really just evades the issue at hand, which is that the browser's DOM isn't designed to be used this way. The "correct" way to handle asynchronous activities (like loading images) is to respond to the various completion events they raise, and to structure your scripts so that their workflow is driven by these events, not the other way round.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date