Skip to content

Instantly share code, notes, and snippets.

@paulirish
Created January 4, 2010 02:38
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 21 You must be signed in to fork a gist
  • Save paulirish/268257 to your computer and use it in GitHub Desktop.
Save paulirish/268257 to your computer and use it in GitHub Desktop.
imagesLoaded() jquery plugin
// $('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;
};
@paulirish
Copy link
Author

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

@cjboco
Copy link

cjboco commented Apr 29, 2011

@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);
}

@ccoenen
Copy link

ccoenen commented Apr 29, 2011

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.

@cjboco
Copy link

cjboco commented Apr 29, 2011

@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.

@paulirish
Copy link
Author

@cjboco

updated the script here with these suggestions.

thanks guys. :)

@aFarkas
Copy link

aFarkas commented Apr 30, 2011

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

@paulirish
Copy link
Author

@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. :)

@Akaryatrh
Copy link

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 ;)

@ccoenen
Copy link

ccoenen commented Jul 8, 2011

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.)

@Akaryatrh
Copy link

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)

@Akaryatrh
Copy link

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)

@paulwehner
Copy link

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?

@cjboco
Copy link

cjboco commented Jul 10, 2011

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

@Akaryatrh
Copy link

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.

@paulwehner
Copy link

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.

@Akaryatrh
Copy link

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.

@paulwehner
Copy link

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

@scottneish
Copy link

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!

@cjboco
Copy link

cjboco commented Jul 21, 2011

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.

@scottneish
Copy link

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").

@ksykulev
Copy link

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.

@ksykulev
Copy link

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

@cjboco
Copy link

cjboco commented Aug 25, 2011

@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.

@ksykulev
Copy link

@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); }
}

@ksykulev
Copy link

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); }
}

@cjboco
Copy link

cjboco commented Aug 25, 2011

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

@ksykulev
Copy link

@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!

@paulirish
Copy link
Author

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

@StephanFischer
Copy link

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

@ahoward
Copy link

ahoward commented Feb 10, 2013

nice! i'm using this here -> https://github.com/ahoward/jquery.bires

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment