views:

52

answers:

3

I am working on a bit of javascript to plot data on a <canvas>. The data points are marked by one of several different (small) image files. I am attempting to have the plot method wait until all the images are loaded. My best attempt thus far is such:

var icon = {
    left : {
        air : new Image(),
        bone : new Image(),
    },
    right : {
        air: new Image(),
        bone : new Image(),
    },
};

icon.left.air.src   = option.imgPath + 'left.air.png';
icon.right.air.src  = option.imgPath + 'right.air.png';
icon.left.bone.src  = option.imgPath + 'left.bone.png';
icon.right.bone.src = option.imgPath + 'right.bone.png';


function main() {
    Canvas.draw();

    // Make sure our image icons are loaded before we try to plot
    $(icon.left.air).load(function() {
        $(icon.right.air).load(function() {
            $(icon.left.bone).load(function() {
                $(icon.right.bone).load(function() {
                    Data.plot();
                });
            });
        });
    });
}

This works as expected most of the time. On occasion, it will fail and no data will be plotted. Inserting several console.log() statements shows that the script will silently stop working through the series of .load() statements, though code that comes after will be executed.

My questions are as follows: Am I approaching this the right way? Is there a way to attach an event to my icon object that will fire once all of the images inside are loaded?

This is a jquery plugin, so obviously jquery-based solutions are just as acceptable as vanilla javascript.

+2  A: 

Forgive me if I'm wrong, but isn't there a flaw in your load logic? What happens if right.air gets loaded before left.air? The innermost load events will never trigger, and the plot won't start then, will it?

I would do this differently: Have four flags and the respective load event set its own flag to true. Check at the end of each event whether all four flags are set to true. If they are, you're good to go.

And what baloo says: You need to bind the load event before assigning a src.

Pekka
@OP: Combine this with baloo's answer and it should solve the problem.
T.J. Crowder
@Pekka: I see what you're saying, and have one question: by the time this is all finished, I'm going to end up with 11 different icons. Should I just have an array of flags to test against? If they aren't loaded, should I just use setInterval with some small value then try again?Actually, that was two questions.
Rookwood
@Rookwood hmm. An array is certainly a good way to go - or a counter, as @Sean suggests. Basically, if you can guarantee all the images get loaded, it should be enough to just have the `load` events. When all 11 are loaded, the last one will trigger the plot (because the required number has been reached). If the loading of an image fails, the plot will not start. Check out @Sean's solution, maybe it already does what you need.
Pekka
+1  A: 

Bind the load() events before you point the Image src's

Or they might be loaded before the event is created

And like Pekka suggests, create all the events separate. Keep a counter or check the .complete property for the images to see if they all are loaded

baloo
@OP: Combine this with Pekka's answer and it should solve the problem.
T.J. Crowder
Most browsers won't start loading the image until the current block (function) finishes. But yes, it would be a good and logic idea to set the load events before setting the srcs.
Felix
Actually, its required for reliable behavior
Sean Kinsey
A: 

This should do the trick. It guarantees that Data.plot() will not be called until after all images has loaded and the main function has run.

...

function collected(count, fn){
    var loaded = 0;
    return function(){
        if (++loaded === count){
             fn();
        } 
    }
}

// four calls + the trigger in 'main'
var onLoad = collected(5, function(){
    Data.plot();
});

icon.left.air.load = onLoad;
icon.right.air.load = onLoad;
icon.left.bone.load = onLoad;
icon.right.bone.load = onLoad;

// set the src AFTER load to make this reliable
icon.left.air.src   = option.imgPath + 'left.air.png';
icon.right.air.src  = option.imgPath + 'right.air.png';
icon.left.bone.src  = option.imgPath + 'left.bone.png';
icon.right.bone.src = option.imgPath + 'right.bone.png';

function main() {
    Canvas.draw();
    onLoad(); // this will be the last (or first, second, third, fourth) call needed to execut Data.plot();
}
Sean Kinsey
Very nice. Will get my upvote once it is confirmed working (can't test right now)
Pekka
This solution works. The only thing to note is that apparently the .load event does not fire if the browser has the image cached. If I wrapped it in jquery $(img).load(function() {onLoad();});it seemed to work perfectly. Thanks to everyone for your input.
Rookwood