Hello, I've an page that will load chunks of HTML using ajax. All those fragments will have images and some of them will be specified in a separate CSS using background-image. I wanted to write a pre-loader, so that the loaded chunk will show up only when all the loading is complete. If all the images would have been in the HTML chunk, I would load it, put it in a hidden element, then look for all the img tags and control if they are loaded (or put some callbacks in the load method). The problem is CSS I can't do that so easily... So the only way that comes to my mind is to have a separate xml file with all the image locations and use it to create IMG elements, load them and when they are loaded display the chunk (so the images have already been pulled from the server). Do you know better approaches? Are there any ready components?
If you accept a piece of advice, don't do it this way. The priority here is the content, images are secondary. Don't let the user wait for the content, because of the images. It will be a pain in the ass, especially on slow connections.
Your best bet it to use some placeholder/indicator images to give feedback about the loading components, and maybe to use some nice fade in effect for images in <img>
tags.
Note: for small images you can try Data: URIs, which allow you to embed images into HTML or CSS files.
UPDATED
Never say no. :) Here is a prototype of what you could use. It works fine for me in FF 3+, and IE 5.5+, but for some reason Chrome shows cssRules to be null. I have to figure out why. Another TODO is to merge @import
-ed rules in IE as it doesn't serve then with the standard rule-set.
Anyways, as you can see you need to pass it a document
, a callback
and there is an optional timeout
argument (10s by default).
The callback
will be executed after every image is loaded in the document. (note if it fails with 404 the script will take care of it). Here you can do your fadeIn or show or somethin'.
The timeout
is there as a fallback, if anything would go wrong, it calls the callback anyway after a certain amount of time.
// this function will execute a callback after all
// images has been loaded in a document
function imageLoader(document, callback, timeout) {
timeout = timeout || 10000; // 10s default
var i, j, remaining = 0; // number of loading images
// hash table to ensure uniqueness
// url => image object
var imgs = {};
function handler(el, event) {
remaining -= 1;
if (remaining == 0 && !callback.done
&& typeof callback === "function") {
callback.done = true;
callback();
}
}
// adds the image to the hash table
function addImage(src, img) {
if (!imgs[src]) {
remaining++;
img = img || new Image();
img.onload = handler;
img.onerror = handler;
img.src = img.src || src;
// add to the hash table
imgs[src] = img;
}
}
// now gather all those images
var sheets = document.styleSheets;
for (i=0; i<sheets.length; i++) {
// HTML <img> tags
var els = document.getElementsByTagName("IMG");
for (i=0; i<els.length; i++) {
var el = els[i].src;
addImage(el.src, el);
}
// CSS background images
var css = sheets[i];
var pos = css.href.lastIndexOf("/");
var cssDir = (pos != -1) ? css.href.substring(0, pos + 1) : "";
var rules = css.cssRules || css.rules;
for (j=0; j<rules.length; j++) {
var style = rules[j].style;
var img = style.backgroundImage;
if (img == "none") img = "";
var filename = img.substring(4, img.length - 1).replace(/['"]/g,"");
if (filename) {
if (filename.indexOf("http://") != 0
&& filename.indexOf("/") != 0) {
filename = cssDir + filename;
}
addImage(filename);
}
}
}
// correct the start time
var start = +new Date();
var timer = setInterval(function(){
var elapsed = +new Date() - start;
// if timout reached and callback
// hasn't been not called yet
if (elapsed > timeout && !callback.done) {
callback();
callback.done = true;
clearInterval(timer);
}
}, 50);
}