views:

49

answers:

1

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?

+1  A: 

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);
}
galambalazs
Usually I'd agree with you. but this time I really need a page showing progress and only when everything is completed to go on.
gotch4
Uh good solution... even because I didn't know it was possible to read through CSS in Js (lazy man I am...). I think this is good, but since I usually have only one Css and I fetch the HTML data from a server, I'd need to add some more complexity by matching the elements that actually use the rules in the HTML i load.... And I think that it isn't gonna be very easy, because of the complexity of rules. Anyway if I can have the designers write one CSS per HTML chunck that is loaded, I could use this approach. It's good starting point.
gotch4
then you may accept the answer. :)
galambalazs
sorry I've been out a few days :D
gotch4