jQuery callback on image load (even when the image is cached)
Asked Answered
R

14

306

I want to do:

$("img").bind('load', function() {
  // do stuff
});

But the load event doesn't fire when the image is loaded from cache. The jQuery docs suggest a plugin to fix this, but it doesn't work

Radloff answered 6/10, 2010 at 21:26 Comment(2)
Since your question things have changed. The broken plugin was moved to a gist and later to a repo with a small working plugin jQuery.imagesLoaded. They fix all the little browser quirks.Reber
Above mentioned JQuery library worked just fine for me. Thanks!Pelops
P
593

If the src is already set, then the event is firing in the cached case, before you even get the event handler bound. To fix this, you can loop through checking and triggering the event based off .complete, like this:

$("img").one("load", function() {
  // do stuff
}).each(function() {
  if(this.complete) {
      $(this).load(); // For jQuery < 3.0 
      // $(this).trigger('load'); // For jQuery >= 3.0 
  }
});

Note the change from .bind() to .one() so the event handler doesn't run twice.

Panther answered 6/10, 2010 at 21:34 Comment(17)
Your solution worked perfect for me but I want to understand something, when this code "if(this.complete)" will run, after the image content loads or before? because as I can understand from this .each that you are looping on all $("img") and may be the image content is empty and the load will not happen. hmmmm, I think I have something missing, it will be nice if you can describe that is going on to understand it better. thanks.Correlate
Since your answer things have changed. There now is a small working plugin jQuery.imagesLoaded. They fix all the little browser quirks.Reber
Works perfectly jQuery. What's the alternative of one and complete in Prototype (the library) ?Jabin
I would add a setTimeout(function(){//do stuff},0) within the 'load' function ... the 0 gives the browser system (DOM) one extra tick to ensure that everything is properly updated.Cyrenaica
@Reber - while I'm currently using and really like the jQuery.imagesLoaded plugin, it doesn't wait for the image if it's a 404 and you're replacing it in the onerror event.Aberdare
@johntrepreneur, I haven't made that library. I suggest you report the issue on its GitHub page.Reber
Great solution but sometimes, it doesn't work in IE when the images are loaded from cache, it also binds a listener when it's not needed, I added an answer that should fix it for most common cases.Doyen
Why to use one instead of on? using one will not show the final dimension of the imageCrystalcrystalline
@SazzadHossainKhan showing the final dimension isn't the point here, and .one() because you want it to fire once and only once.Panther
@Reber - it's a HUGE plugin, not small at all by any scale!!Ketchan
Well, @vsync, at the time of writing the plugin was a lot smaller. github.com/desandro/imagesloaded/tree/…Reber
GREAT answer - added one little filter to the each statement: if(this.complete && $(this).width() > 0) $(this).load(); without this, on browser refreshes, I was getting 0 for image width. With this addition, the call to load ensures that the image has an actual width. Works perfectly with cached images as-is thoughReareace
@Ketchan Not sure I'd call 9KB huge?Sedgewake
@Sedgewake - over 2 years ago when I wrote that comment, perhaps. I don't remember. I see the packaged version was ~26 KB it was significantly reducedKetchan
Note from 2017, .bind is deprecated, use .on insteadParis
since JQuery 3 method .load() does not trigger event and has 1 required argument, use .trigger("load") insteadSickroom
You should modify the answer since in jQuery 3, this does not work and you have to use .trigger("load") instead.Thralldom
C
55

Can I suggest that you reload it into a non-DOM image object? If it's cached, this will take no time at all, and the onload will still fire. If it isn't cached, it will fire the onload when the image is loaded, which should be the same time as the DOM version of the image finishes loading.

Javascript:

$(document).ready(function() {
    var tmpImg = new Image() ;
    tmpImg.src = $('#img').attr('src') ;
    tmpImg.onload = function() {
        // Run onload code.
    } ;
}) ;

Updated (to handle multiple images and with correctly ordered onload attachment):

$(document).ready(function() {
    var imageLoaded = function() {
        // Run onload code.
    }
    $('#img').each(function() {
        var tmpImg = new Image() ;
        tmpImg.onload = imageLoaded ;
        tmpImg.src = $(this).attr('src') ;
    }) ;
}) ;
Celestyna answered 6/10, 2010 at 21:33 Comment(5)
You should try to attach the load handler before it's added, also this won't work but for one image, the OPs code works for many :)Panther
Thanks, I have added an updated version which I hope addresses these two issues.Celestyna
#img is an ID not an element selector :) Also this.src works, no need to use jQuery where it isn't needed :) But creating another image seems like overkill in either case IMO.Panther
it should be $('img') . but solution is gr8 in 2k15 alsoRuching
Also, for the multiple image case, if you want to operate on the DOM element for each image in imageLoaded, then use tmp.onload = imageLoaded.bind(this)Homage
D
41

My simple solution, it doesn't need any external plugin and for common cases should be enough:

/**
 * Trigger a callback when the selected images are loaded:
 * @param {String} selector
 * @param {Function} callback
  */
var onImgLoad = function(selector, callback){
    $(selector).each(function(){
        if (this.complete || /*for IE 10-*/ $(this).height() > 0) {
            callback.apply(this);
        }
        else {
            $(this).on('load', function(){
                callback.apply(this);
            });
        }
    });
};

use it like this:

onImgLoad('img', function(){
    // do stuff
});

for example, to fade in your images on load you can do:

$('img').hide();
onImgLoad('img', function(){
    $(this).fadeIn(700);
});

Or as alternative, if you prefer a jquery plugin-like approach:

/**
 * Trigger a callback when 'this' image is loaded:
 * @param {Function} callback
 */
(function($){
    $.fn.imgLoad = function(callback) {
        return this.each(function() {
            if (callback) {
                if (this.complete || /*for IE 10-*/ $(this).height() > 0) {
                    callback.apply(this);
                }
                else {
                    $(this).on('load', function(){
                        callback.apply(this);
                    });
                }
            }
        });
    };
})(jQuery);

and use it in this way:

$('img').imgLoad(function(){
    // do stuff
});

for example:

$('img').hide().imgLoad(function(){
    $(this).fadeIn(700);
});
Doyen answered 3/12, 2013 at 12:20 Comment(7)
urm.. and what if my images have a size set with css?Deemphasize
Set width, max-height and leave height:auto, often it's necessary to set both width and height only if you need to stretch the image; for a more robust solution I would use a plugin like ImagesLoadedDoyen
Am I right that wrapping selector in $() - i.e., $(selector).each(...) - is redundant, given that you already have called the function with a jQuery object - onImgLoad($('img'),...)?Centaur
yes it was a mistyping, jsdoc was ok, I corrected the line exampleDoyen
I've searched for hours for a solution why the load handler doesn't work correctly on Firefox, and this is the only solution that really helped to make it work!Dorton
This solution with $.fn.imgLoad work across browsers. Fixing it will be even better: && /*for IE 10-*/ this.naturalHeight > 0 This line won't take css style to consideration because img can be blocked but have height. Work in IE10Decretal
This does have a (small) race condition. The image can be loading in a different thread to the script and so can load between the check of complete and attaching the onload event, in which case nothing will happen. Normally JS is synchronous with the rendering and so you don't have to worry about race conditions but this is an exception. html.spec.whatwg.org/multipage/…Verla
D
24

Do you really have to do it with jQuery? You can attach the onload event directly to your image as well;

<img src="/path/to/image.jpg" onload="doStuff(this);" />

It will fire every time the image has loaded, from cache or not.

Diuretic answered 6/10, 2010 at 21:34 Comment(6)
It is cleaner without inline javascriptSelda
Hi, Bjorn, will the onload handler fire when the image is loaded a second time from cache?Blackbeard
@Cupidvogel no it won't.Mnemosyne
Dude, you saved my life, I've tried at least a dozen other solutions and yours is the only one that actually worked. I'm seriously thinking about creating 10 more accounts to vote your answer up 10 more times. Seriously thanks!Entellus
I've never understood people's aversion to inline JS. Image loading happens separately and out of sequence from the rest of the page loading process. Therefore, this is the only valid solution to getting immediate feedback about image loading completion. However, if someone needs to wait for jQuery to load and that happens later, then they will have to not use the simple solution here and use one of the other solutions (Piotr's is probably the best option since it isn't dependent on pseudo-hacks but guari's height() check might be important too). A processing queue could be useful as well.Quotidian
@Mnemosyne yes it will and does.Diddle
C
17

You can also use this code with support for loading error:

$("img").on('load', function() {
  // do stuff on success
})
.on('error', function() {
  // do stuff on smth wrong (error 404, etc.)
})
.each(function() {
    if(this.complete) {
      $(this).load();
    } else if(this.error) {
      $(this).error();
    }
});
Catiline answered 20/3, 2014 at 10:54 Comment(2)
This one worked best for me. I think the key is to set the load handler first and then call it later in the each function.Trickery
setting image using below code ''var img = new Image(); img.src = sosImgURL; $(img).on("load", function () {''Bailar
C
13

I just had this problem myself, searched everywhere for a solution that didn't involve killing my cache or downloading a plugin.

I didn't see this thread immediately so I found something else instead which is an interesting fix and (I think) worthy of posting here:

$('.image').load(function(){
    // stuff
}).attr('src', 'new_src');

I actually got this idea from the comments here: http://www.witheringtree.com/2009/05/image-load-event-binding-with-ie-using-jquery/

I have no idea why it works but I have tested this on IE7 and where it broke before it now works.

Hope it helps,

Edit

The accepted answer actually explains why:

If the src is already set then the event is firing in the cache cased before you get the event handler bound.

Circumfluent answered 13/12, 2012 at 13:29 Comment(3)
I've tested this is in a lot of browsers and haven't found it failing anywhere. I used $image.load(function(){ something(); }).attr('src', $image.attr('src')); to reset the original source.Eucaine
I've found that this approach does not work on iOS, at least for Safari/601.1 and Safari/602.1Unkenned
Would be awesome to find out why, what browser is it in?Circumfluent
U
5

By using jQuery to generate a new image with the image's src, and assigning the load method directly to that, the load method is successfully called when jQuery finishes generating the new image. This is working for me in IE 8, 9 and 10

$('<img />', {
    "src": $("#img").attr("src")
}).load(function(){
    // Do something
});
Upsydaisy answered 24/6, 2013 at 17:10 Comment(1)
For those that use Ruby on Rails, with remote request, using __rjs.erb templates, this is the only solution I've found. Because using partials in js.erb, it looses de binding ($("#img".on("load")... and for images is NOT possible to delegate the "on" method: $("body").on("load","#img",function()...Saransk
L
5

A solution I found https://bugs.chromium.org/p/chromium/issues/detail?id=7731#c12 (This code taken directly from the comment)

var photo = document.getElementById('image_id');
var img = new Image();
img.addEventListener('load', myFunction, false);
img.src = 'http://newimgsource.jpg';
photo.src = img.src;
Lindon answered 22/6, 2017 at 14:1 Comment(1)
This is exactly what I was looking for. A single image comes to me from the server. I wanted to preload it and then serve. Not selecting all images like many answers do. Good one.Henson
A
4

A modification to GUS's example:

$(document).ready(function() {
    var tmpImg = new Image() ;
    tmpImg.onload = function() {
        // Run onload code.
    } ;

tmpImg.src = $('#img').attr('src');
})

Set the source before and after the onload.

Antigone answered 6/10, 2010 at 21:36 Comment(1)
Like @Gus's answer, this won't work for multiple images...and there's no need to set the src before the attaching the onload handler, once afterwards will suffice.Panther
S
3

Just re-add the src argument on a separate line after the img oject is defined. This will trick IE into triggering the lad-event. It is ugly, but it is the simplest workaround I've found so far.

jQuery('<img/>', {
    src: url,
    id: 'whatever'
})
.load(function() {
})
.appendTo('#someelement');
$('#whatever').attr('src', url); // trigger .load on IE
Smedley answered 14/11, 2013 at 15:59 Comment(0)
H
1

I can give you a little tip if you want do like this:

<div style="position:relative;width:100px;height:100px">
     <img src="loading.jpg" style='position:absolute;width:100px;height:100px;z-index:0'/>
     <img onLoad="$(this).fadeIn('normal').siblings('img').fadeOut('normal')" src="picture.jpg" style="display:none;position:absolute;width:100px;height:100px;z-index:1"/>
</div>

If you do that when the browser caches pictures, it's no problem always img shown but loading img under real picture.

Hiddenite answered 13/2, 2012 at 3:10 Comment(0)
A
1

I had this problem with IE where the e.target.width would be undefined. The load event would fire but I couldn't get the dimensions of the image in IE (chrome + FF worked).

Turns out you need to look for e.currentTarget.naturalWidth & e.currentTarget.naturalHeight.

Once again, IE does things it's own (more complicated) way.

Ashanti answered 12/10, 2012 at 8:4 Comment(0)
L
0

You can solve your problem using JAIL plugin that also allows you to lazy load images (improving the page performance) and passing the callback as parameter

$('img').asynchImageLoader({callback : function(){...}});

The HTML should look like

<img name="/global/images/sample1.jpg" src="/global/images/blank.gif" width="width" height="height" />
Luciusluck answered 9/1, 2011 at 10:17 Comment(0)
R
-2

If you want a pure CSS solution, this trick works very well - use the transform object. This also works with images when they're cached or not:

CSS:

.main_container{
    position: relative;
    width: 500px;
    height: 300px;
    background-color: #cccccc;
}

.center_horizontally{
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: green;
  left: 50%;
  top: 0;
  transform: translate(-50%,0);
}

.center_vertically{
  position: absolute;
  top: 50%;
  left: 0;
  width: 100px;
  height: 100px;
  background-color: blue;
  transform: translate(0,-50%);
}

.center{
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100px;
  height: 100px;
  background-color: red;
  transform: translate(-50%,-50%);
}

HTML:

<div class="main_container">
  <div class="center_horizontally"></div>
  <div class="center_vertically"></div>
  <div class="center"></div>
  </div>
</div

Codepen example

Codepen LESS example

Reareace answered 10/6, 2016 at 18:35 Comment(1)
Could you give an example of how this works with image loading?Triumph

© 2022 - 2024 — McMap. All rights reserved.