views:

997

answers:

4

I'm trying to write a plugin that has a built in function to wait until all images that are on the page to be loaded before it executes itself. $(window).load() only works if it's the initial load of the page, but if someone wants to pull down some HTML through AJAX that contains images, it doesn't work.

Is there any good way of doing this AND implementing it so that it can be self-contained in the plug-in? I've tried looping over $('img').complete, and I know that doesn't work (the images never load, and the browser crashes under a "script takes too long to complete" bug). For an example of what I'm trying to do, visit the page I'm looking to house the plugin at:

http://www.simplesli.de

If you go to the "more uses" section (click it on the nav bar), you'll see that the image slideshow won't load properly. I know the current code doesn't work, but it's just holding place until I figure something out.

Edit: Trying this solution, but it just doesn't work:

if($('.simpleSlide-slide img').size() > 0) {
    var loaded_materials = $('.simpleSlide-slide img').get();
} else {
    var loaded_materials = $('.simpleSlide-slide').get();
}

$(loaded_materials).live('load', function() {
         /* Image processing and loading code */
    });
+1  A: 

Have you thought of using live() to bind to all current and future img elements?

$('img').live('load', function ()
{
    alert("image loaded: "+this.src);
});

After your update, I can see the problem you're having. live() works best on a selector, whereas your using it on a jQuery-wrapped array of elements. Using live() like this is no different to using bind(). You should try something like this instead:

if($('.simpleSlide-slide img').size() > 0) {
    var loaded_materials = '.simpleSlide-slide img';
} else {
    var loaded_materials = '.simpleSlide-slide';
}

$(loaded_materials).live('load', function() {
         /* Image processing and loading code */
});
Andy E
I tried this last night, and it didn't seem to work, unless I just totally missed the syntax, but live doesn't seem that complicated to me. I believe it doesn't inherently support `'load'` as an event though...
dclowd9901
You're a machine! I was in the middle of typing my answer :P That's unfair ;)
naugtur
@dclowd9901: the documentation makes no mention of unsupported events. Just `A string containing a JavaScript event type, such as "click" or "keydown".` It even claims to support custom events, so I'm sure it can support the standard ones :-)
Andy E
@Andy Yes it does support all events, but only in the latest version of jquery. It used to work only with a subset of avaliable events and they had to fix it quick, because people kept on abusing the live() function. I myself prefer to keep track of what I'm doing and use bind() if it's possible at all.
naugtur
@naug: then why isn't it working in the code above?
dclowd9901
@dclowd9901: see my update.
Andy E
Tried it again. It's like it's not a functional use of .live() or something. With Firebug breakpointing, it gets to that line, then just takes a dump. It doesn't go any further.
dclowd9901
+1  A: 

I think You need to bind a function to global ajaxSuccess and then bind a load event to every image on page.

$.ajaxSuccess(function(){
 $('img').load(function(){
  //Your code here
 });
});

or when You expect lots of images:

$.ajaxSuccess(function(){
 $('img:not(.loaded)').addClass('.loaded').bind('load',function(){
  //Your code here
 });
});

If any of the above code doesn't work properly Tou might want to put the code in a callback that adds html to the site after adding it. using setTimeout in the above function might help too.

[edit]

You can generate images <img src="something" onload="$.imgHello()" /> in server-side code and increment some counter in each $.imgHello() function call if You know how many images are there going to be loaded. But this is not a solution that I'd recommend to anyone.

naugtur
What's the point of adding the `.loaded` class?
dclowd9901
If You happen to have hundreds of images on the page it should be faster to select only the new ones than to bind new load function to all of them.
naugtur
But I would be executing the same function for as many images as I have on the page? Wouldn't that just as much slow everything down? I keep coming back to this idea that if there was just some way I could have the script know how many images were on the page (which it can), and each image could notify the script of its completion in some way, then the script could in turn execute a function upon that notification of completion, then I'd be good to go. Is javascript even that capable?
dclowd9901
In fact that's what happens. But only for images generated by a single ajax call. You can't bind anything to an event BEFORE putting the image on page. There is only one little hack You can do. see edit
naugtur
A: 

After trying to accomplish this task in just about every conceivable fashion, this one was the one that finally worked, and a big thanks to Naugtur for pointing me in the right direction:

var no_of_images = $('img').size();

$.extend(no_of_images);

if(no_of_images > 0) {

    var images = new Array();
    var i = 0;
    $('img').each( function() {
        images[i] = $(this).attr('src');
        i++;
    });

    i = 0;

    $(images).each( function(){
        var imageObj = new Image();
        imageObj.src = images[i];
        $(imageObj).load( function() {
            no_of_images--;
            i++;
            if(no_of_images == 0){
                functionToPerform();
            }
        });
    });
} else {
    functionToPerform();
}

For a quick explanation of what's happening, I'll use an analogy. Imagine each image is a guest at your party. The lights are out. You flip on the lights, then race to each guest and give them a piece of paper, a pen and a cell phone, all the while tallying how many people you handed materials out to. They all start drawing furiously and then as soon as they finish their drawings, they call you and tell you. For each one that calls, you cross a tally off your board. When the last person calls you (when you run out of tally marks on your board), you tell them to hang up and call the pizza guy. It's pizza time. If there were no guests to begin with, you call the pizza guy yourself.

If you're thinking "Why did he create an Image() object for each $.load(), well, I tried going the traditional jQuery route of using a straight jQuery-style selector instead of the image Object. It was seemingly completely ignored (which crashed the page). I tried using Image() in conjunction with .onLoad, and that worked insofar as not crashing the page, but it didn't perform the function for which I wrote it.

This bit of code is self-contained, so I imagine it can be copypasta'd into any image-loading app. It seems small enough to me so as not to be intrusive. functionToPerform() basically acts as any function you would like to wait to perform until all images are loaded (in my case, it was the entire plugin). Toodles.

dclowd9901
+2  A: 

The problem with looping over the images and calling $('img').complete is that the browser UI is single threaded, so you're never giving it chance to load the images because it's always in your loop. You can use setTimeout to allow the browser to work. e.g.,

$.get("whatever",function(html) {
   $('#foo').html(html);
   var images = getTheImages();
   function checkImages() {
      var done = true;
      $.each(images,function() {
         done = done && this.complete;
         return done;
      });
      if(done) {
        fireEvent();
      } else {
        setTimeout(checkImages,100); // wait at least 100 ms and check again
      }
   }
});
noah
I actually tried this as well. It still didn't fire back at the right time. Perhaps my method was wrong, but it looked very similar to what you wrote up here.
dclowd9901