Article
Deck the Halls with Unobtrusive JavaScript
Wrapping Everything Else
This technique isn't only limited to textual content; we can also add loaders to images. Instead of manually triggering the switch from loader to content, however, we'll set up an event handler that detects when the browser is finished downloading the image. We'll do this through its onload event handler. Once the event is triggered by the browser, our code will handle the switch.
In this example, we'll do things a little differently just so we can explore the different implementation possibilities. In the earlier examples, we manipulated an element's style object directly through JavaScript. This approach may not always be appropriate, as designers may want more direct control over the different states of an element through CSS. So for this example, we'll be defining a loading class name that will be assigned to elements that are, well, loading. Once the loading is complete, all we'll do is remove the class name.
Let's start with the markup:
loader-img.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" href="loader-img.css">
<link rel="stylesheet" type="text/css" media="all" href="style.css">
<script>
document.write('<link rel="stylesheet" type="text/css" href="loader-img-js.css">');
</script>
</head>
<body>
<ul id="thumbnails">
<li class="loading"><img src="img1.jpg"></li>
<li class="loading"><img src="img2.jpg"></li>
<li class="loading"><img src="img3.jpg"></li>
<li class="loading"><img src="img4.jpg"></li>
<li class="loading"><img src="img5.jpg"></li>
<li class="loading"><img src="img6.jpg"></li>
<li class="loading"><img src="img7.jpg"></li>
<li class="loading"><img src="img8.jpg"></li>
<li class="loading"><img src="img9.jpg"></li>
<li class="loading"><img src="img10.jpg"></li>
<li class="loading"><img src="img11.jpg"></li>
<li class="loading"><img src="img12.jpg"></li>
<li class="loading"><img src="img13.jpg"></li>
<li class="loading"><img src="img14.jpg"></li>
<li class="loading"><img src="img15.jpg"></li>
</ul>
<script src="loader-img.js"></script>
<p class="caption"><strong>Image Credit:</strong> <a href="http://www.sxc.hu/profile/danzo08/">Daniel Wildman</a></p>
</body>
</html>
What we have here is a simple list of images. Each list item is given the class name loading, since we know that at the time of the DOM's creation, the images have yet to be downloaded.
We've also included two CSS files: one that contains basic layout rules, and another, linked via a JavaScript document.write statement, which hides content that will be made visible later by JavaScript:
loader-img.css
#thumbnails {
list-style-type: none;
width: 375px;
}
#thumbnails li {
width: 125px;
height: 125px;
float: left;
}
loader-img-js.css
#thumbnails li.loading {
background: url(loader-big.gif) no-repeat center center;
}
#thumbnails li.loading img {
visibility: hidden;
}
Lastly, and most importantly, here's the script that implements a loader for each of our images:
loader-img.js
var thumbs = document.getElementById("thumbnails");
if (thumbs) {
var imgs = thumbs.getElementsByTagName("img");
if (imgs.length > 0) {
for (var i = 0; imgs[i]; i = i + 1) {
var img = imgs[i];
var newImg = img.cloneNode(false);
img.parentNode.insertBefore(newImg, img);
newImg.onload = function () {
var li = this.parentNode;
li.className = li.className.replace("loading", "");
};
newImg.src = img.src;
img.parentNode.removeChild(img);
}
}
}
Here, we grab the container element that surrounds our thumbnails, and all the images within it. Now, normally we'd just loop over the images and assign an onload event handler to each of them. Unfortunately, Firefox doesn't fire the onload event on images that are already in the cache, so our script won't work on subsequent visits to the page. In order to work around this issue, we simply clone the image, and replace the original with its clone. The act of inserting the newly cloned image into the document ensures that its onload event will fire.
Another point to note is that Internet Explorer and Opera require that the onload event handler be assigned before the src attribute. Otherwise, they won't fire the onload event. When the event is triggered, the script will remove the class name loading from the image's parent element. This, in turn, causes the list item to lose its rotating background image, and the image to lose the visibility: hidden; declaration that was hiding it. In my opinion, class name manipulation is by far the most elegant way to toggle an element's style, because it keeps all of the presentation information in a separate file that's dedicated to the task. It also allows future changes to be made to style rules via modifications to the CSS -- without requiring us to open up a JavaScript file.
In case this example runs too quickly for you, I've put together another one that simulates a random delay, so you can get an idea of what this example would look like on a slower connection.
Post-tryptophan Adventures
We've explored a few of the ways to safely deal with unobtrusive JavaScript timing issues. We've also looked at how to deal with timing issues in general by incorporating loaders as placeholders for our hidden content. I hope this tutorial has encouraged you to think creatively and unobtrusively (don't forget to download the complete code). Now take what you've learned here and create wonderfully unobtrusive, gracefully degradable, and joyous web sites. Ho! Ho! Ho!