public
Last active

imagesLoaded() jquery plugin

  • Download Gist
README.md
Markdown
gistfile1.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// $('img.photo',this).imagesLoaded(myFunction)
// execute a callback when all images have loaded.
// needed because .load() doesn't work on cached images
 
// mit license. paul irish. 2010.
// webkit fix from Oren Solomianik. thx!
 
// callback function is passed the last image to load
// as an argument, and the collection as `this`
 
 
$.fn.imagesLoaded = function(callback){
var elems = this.filter('img'),
len = elems.length,
blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
elems.bind('load.imgloaded',function(){
if (--len <= 0 && this.src !== blank){
elems.unbind('load.imgloaded');
callback.call(elems,this);
}
}).each(function(){
// cached images don't fire load sometimes, so we reset src.
if (this.complete || this.complete === undefined){
var src = this.src;
// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
// data uri bypasses webkit log warning (thx doug jones)
this.src = blank;
this.src = src;
}
});
 
return this;
};

just a general comment:
In Case, there are no images in the selection, the callback will never be called. One could argue for either of these interpretations:

  • No images have been loaded, therefore the callback won't be called
  • All images that are present are available (i.e. none at all), the callback should be called.

I currently have a situation in which i need the second behaviour (output from a CMS which may or may not have images in it. I simply added the following in line 15:

if (len < 1) {
    callback.call();
    return this; // return early
}

I'm just sharing it in case anyone else has a similar situation or other thoughts about it.

I've looked for something like this several times and found a few slightly different approaches but nothing this reliable. In IE7/8 though the event seems to get hit twice for me unless the image is cached.

$('#myImage').imagesLoaded(function(img) {
var height = img.height();
});

this doesn't seem to be working in ie9 for me... doh!

$('#main img').imagesLoaded(function() { $(this).fadeIn(); });

I've heard some issues with ie9 recently.. I dont have time to update this script so someone else will have to dig in.

@Phil-B - I think it's being called twice because of the data:uri (i.e. it's setting the src twice, once for the data:uri and another for the real image src)

I recommend wrapping the callback with a check of the src, like so:

if (this.src !== "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==") {
callback.call(elems, this);
}

in that case, it's probably best to create a variable beforehand which would be used in Line 24 (of the version shown above) and in cjboco's suggestion.

@ccoenen - Yeah, I already did that in my script: (I like small scripts!) I added the variable:

blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";

and then use that for the checks.

@cjboco

updated the script here with these suggestions.

thanks guys. :)

I just saw this gist. Here are my suggestions:

  1. Use $.event.special to "patch" jQuery's event method (developer should use simply bind('load', fn))
  2. There might be some other events to patch (for example, window.onload, if a script is loaded dynamically etc.)

You can see an untestet version of this @https://gist.github.com/949545

The script includes load for img and window and loadedmetadata

@afarkas

we have another script that took this route, https://github.com/peol/jquery.imgloaded/blob/master/ahpi.imgload.js

i wasnt too involved with the development of it but i know they hit some roadblocks. however they do have a test suite, which.. i do not. :)

Hi Paul,

Do you plan to make a complete webpage that explains (with examples) all possibilities of this plug-in ?
I have difficulties to understand if this plug-in iretates through all images that have a specific class (ex : $('.myclass').imagesLoaded(myfunction) and fires callback for the last image loaded or if it can only check an image at once…
Then, i don't understand what you mean by "callback function is passed the last image to load as an argument, and the collection as this"…

And thx for all the great job you provide to the jQuery community ;)

You may use it to iterate over many images. Lines 17 and 19 work on multiple elements (the bind always works on many elements in a collection, and each explicitly goes over all items in a list).

Your example should work just fine, as long as you have images with a class like myclass, i.e. awesome photo (or multiple thereof.)

Hey ccoenen, thx for this quick reply :)

Still i don't see how i could trace which image has just been loaded, or at least retrieve a list of loaded images.

For example :
var images = $(".myClass");
images.imagesLoaded(function(imgList){
imgList.each(function(){
console.log($(this).attr('src'));
});

});

NB : i know i can retrieve images list without the plug-in, but i just want to understand how it can be used (like calling a function after each image is loaded)
NB 2 : one more thing, i don't know if it's a bug or if it comes from my script, but it seems the load event is called multiple times. The first time, it logs 1 load, the second time it logs 3 loads, third time it logs 5 loads, etc…

EDIT : i uploaded a demo of this bug here : http://sebastien.vaneyck.free.fr/demo_load/ (check console logs in Safari, Chrome, Webkit, Opera, or Firefox with Firebug)

Ok, i found a fix !
We just need to unbind the load event, before binding it :

line 17 : elems.unbind('load').bind('load',function(){ // what goes next });

(i updated my demo with this fix)

The imagesLoaded() plugin doesn't seem to be working for my in this example... http://jsfiddle.net/aaairc/fANSQ/1/ ... using my Mac's Chrome Browser. To test, click the "Click Me" div and see if boxHeight is printed out as 36 or 543. If it prints out as 36, that means (I believe) that when the height of the popup was measured, .imagesLoaded didn't wait for the image within the popup to load.

For my computer, boxHeight prints out correctly in FF, Safari, and IE. In Chrome 12.0.742.112 on my Mac, it prints out as 36. Strange behavior though... apparently boxHeight was measured correctly when I asked someone else on IRC who had a Mac to test. Not sure if his version of Chrome is the same.

I tried clearing the cache and cookies off my Chrome Browser, but still the same behavior. So after that longwinded explanation:

1) Can anyone else see this bug, where boxHeight is printed as 36 in Chrome, instead of the actual height of the box of 543?

2) If they can... how do you fix it?

@aaairc, I'm not a jFiddle expert, but I don't see that you've included the imagesLoaded plug-in in the example?

The plug-in is included in his file called "jquery.isotope.js" (check "manage resources" on left menu) : http://isotope.metafizzy.co/jquery.isotope.min.js
I'm personnaly on a Mac, and testing your example on Chrome 12.0.742.112.

boxHeight returns me 543, so i can't reproduce your bug.

hmmm not sure why it would be something specific to just my chrome browser, but I'll try and reinstall my browser and see if it continues.

Oh just a note : why did you declare your functions (adjustHTML() and adjustPosition() ) out of the $('document').ready(), at the end of your script ? This is not valid (according to JSLint), you can wrap them inside to prevent any error or bug.

I did it because I don't know what I'm doing :). Just learning jQuery. Thanks for the heads up.

Ok. To anyone that is having issues with IE not triggering consistently, a friend put me on to a solution that seems to work...

current code (line 17):
elems.bind('load',function(){

revised code -> i is an integer for iterating through all images in 'elems'; I had to wrap the code from line 17-28 in a for loop to iterate through.
$("〈img src = " + elems[i].src + "/〉").bind('load',function(){
(make sure to relpace the < > when you paste)

Hope this helps somebody!

Wouldn't that suggest that "this.filter("img")" isn't working properly in IE? Since in theory, elems should already be an array of images.

I don't believe it's the filter, but how IE handles the element passed into the .load function... I have had issues when passing something as simple as $("#myImage").

You should probably unbind the load event when the image is loaded (line 17).
...
elems.bind('load',function(){
$(this).unbind('load');
if (--len <= 0 && this.src !== blank){ callback.call(elems,this); }
}
...
I abuse this script by replacing the src for some images multiple times, which binds the load event multiple times. This in turn causes the callback to be called many times on subsequent calls to imagesLoaded, since the old load events have 'len' set to zero on completion.

Here's my revised version.. https://gist.github.com/1170048
Line 18 has the notable change.

@ksykulev - Just a quick thought, would this cancel the load event on the real image, since this would unbind the load event when setting the blank image? Haven't tested it yet, just curious.

@cjboco - hmmm good point, i didn't think about that..
In my particular case I have a gallery of images, and i give the user the ability to switch between the small or large res. images. I need to rescale some of elements around the images, so I use this script to let me know when all the images are loaded.
It appears to work OK in webkit... but some more thorough testing is definitely needed.

Not sure if this helps, but I also namespaced the binded load event like this:
elems.bind('load.imagesLoaded',function(){
$(this).unbind('load.imagesLoaded');
if (--len <= 0 && this.src !== blank){ callback.call(elems,this); }
}

Yes that does break it in webkit, scratch that
Only way around that is to break the cache:
src + ?_="+(new Date().getTime())
:(
Thanks for pointing that out.

Could the unbinding happen inside the callback after all the images have loaded?
elems.bind('load.imagesLoaded',function(){
if (--len <= 0 && this.src !== blank){ elems.unbind('load.imagesLoaded'); callback.call(elems,this); }
}

@ksykulev - maybe add it after "this.src = src"?

@cjboco - After some testing:
Putting the unbind after the "this.src = src" unbinds the load event too fast and it doesn't get called..
Putting the unbind in the conditional seems to be working OK.

elems.bind('load.imagesLoaded',function(){
if (--len <= 0 && this.src !== blank){ elems.unbind('load.imagesLoaded'); callback.call(elems,this); }
}

@cjboco - Thanks for your help!

hey guys

mr @desandro has picked up this script and given it a proper repo as a home:

https://github.com/desandro/imagesloaded

I've added the namespaced events to my gist above as that seems like a good idea. but in general, david's version is the new canonical one

ok, its a cached images solution for webkit? but its simpler by checking width or height after loading event:

    img.addEventListener("load", function() 
    {
      var tmrLoaded = window.setInterval(function()
      {
        if (img.width) 
        {
          window.clearInterval(tmrLoaded);  
          alert( "its done!");
        }
      }, 100);

    }, false);

Greetz

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.