295

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

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
Tom Lehman
  • 75,197
  • 69
  • 188
  • 262
  • 12
    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](https://github.com/desandro/imagesloaded)**. They fix all the [little browser quirks](https://github.com/desandro/imagesloaded/wiki/Browser-quirks). – Lode Nov 10 '11 at 12:28
  • Above mentioned JQuery library worked just fine for me. Thanks! – plang Dec 10 '12 at 10:42
  • Thank you for the plugin, it worked fine. – onimojo Dec 20 '12 at 18:01

14 Answers14

582

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.

xyres
  • 16,063
  • 3
  • 42
  • 65
Nick Craver
  • 594,859
  • 130
  • 1,270
  • 1,139
  • 8
    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. – Amr Elgarhy May 18 '11 at 00:23
  • 36
    Since your answer things have changed. There now is a **small working plugin [jQuery.imagesLoaded](https://github.com/desandro/imagesloaded)**. They fix all the [little browser quirks](https://github.com/desandro/imagesloaded/wiki/Browser-quirks). – Lode Nov 10 '11 at 12:30
  • Works perfectly jQuery. What's the alternative of `one` and `complete` in Prototype (the library) ? – Steffi Apr 06 '12 at 08:31
  • 1
    I love brilliant and simple solutions! – Nick Johnson Jun 11 '13 at 14:10
  • 3
    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. – dsdsdsdsd Jul 13 '13 at 10:29
  • @Lode - while I'm currently using and really like the [jQuery.imagesLoaded](https://github.com/desandro/imagesloaded) plugin, it doesn't wait for the image if it's a 404 and you're replacing it in the `onerror` event. – johntrepreneur Aug 30 '13 at 18:09
  • @johntrepreneur, I haven't made that library. I suggest you report the issue on its GitHub page. – Lode Aug 30 '13 at 19:48
  • 1
    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. – guari Dec 03 '13 at 13:23
  • 1
    Why to use `one` instead of `on`? using `one` will not show the final dimension of the image – itsazzad Apr 21 '14 at 00:48
  • @SazzadHossainKhan showing the final dimension isn't the point here, and `.one()` because you want it to fire *once* and only once. – Nick Craver Apr 21 '14 at 00:59
  • 14
    @Lode - it's a HUGE plugin, not small at all by any scale!! – vsync May 05 '14 at 17:36
  • Well, @vsync, at the time of writing the plugin was a lot smaller. https://github.com/desandro/imagesloaded/tree/47bbefce5c766c43ba1ef754b5ecbfa83be55590 – Lode May 06 '14 at 13:21
  • Thanks @NickCraver . Worked awesomely for me :) – Perry Jun 15 '15 at 12:47
  • 1
    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 though – neoRiley Jun 10 '16 at 17:42
  • @vsync Not sure I'd call 9KB huge? – NickG Jul 19 '16 at 14:24
  • @NickG - 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 reduced – vsync Jul 19 '16 at 14:41
  • 1
    Note from 2017, `.bind` is deprecated, use `.on` instead – evilReiko Sep 27 '17 at 06:56
  • 6
    since JQuery 3 method `.load()` does not trigger event and has 1 required argument, use `.trigger("load")` instead – ForceUser Feb 07 '18 at 15:59
  • You should modify the answer since in jQuery 3, this does not work and you have to use .trigger("load") instead. – Esko Oct 15 '18 at 07:37
50

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') ;
    }) ;
}) ;
Gus
  • 6,699
  • 2
  • 23
  • 22
  • 1
    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 :) – Nick Craver Oct 06 '10 at 21:37
  • Thanks, I have added an updated version which I hope addresses these two issues. – Gus Oct 06 '10 at 21:56
  • `#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. – Nick Craver Oct 06 '10 at 21:57
  • it should be $('img') . but solution is gr8 in 2k15 also – Dimag Kharab Feb 22 '15 at 05:47
  • 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)` – blisstdev Jan 22 '20 at 23:48
40

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);
});
guari
  • 3,496
  • 3
  • 26
  • 23
  • urm.. and what if my images have a size set with css? – Simon_Weaver Sep 27 '14 at 21:49
  • 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 [ImagesLoaded](https://github.com/desandro/imagesloaded) – guari Aug 11 '15 at 21:21
  • 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'),...)`? – Dan Nissenbaum May 18 '16 at 03:38
  • 1
    yes it was a mistyping, jsdoc was ok, I corrected the line example – guari May 20 '16 at 11:12
  • 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! – techfly May 19 '17 at 09:32
  • 1
    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 IE10 – Patryk Padus Jul 25 '18 at 10:30
  • 1
    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. https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-complete – Mog0 Sep 10 '18 at 15:17
23

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.

Björn
  • 27,479
  • 9
  • 63
  • 81
  • 5
    It is cleaner without inline javascript – hazelnut Dec 08 '13 at 23:24
  • 1
    Hi, Bjorn, will the `onload` handler fire when the image is loaded a second time from cache? – SexyBeast Jun 22 '14 at 11:54
  • 1
    @Cupidvogel no it won't. – Ads May 19 '15 at 06:16
  • 3
    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! – Carlo Aug 22 '15 at 09:52
  • 1
    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. – CubicleSoft Mar 17 '17 at 14:13
  • @Ads yes it will and does. – Sajjan Sarkar Sep 10 '18 at 13:25
16

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();
    }
});
Community
  • 1
  • 1
Piotr
  • 368
  • 4
  • 11
  • 1
    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. – GreenRaccoon23 Jul 27 '16 at 14:54
  • 1
    setting image using below code ''var img = new Image(); img.src = sosImgURL; $(img).on("load", function () {'' – imdadhusen Apr 28 '20 at 11:24
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.

Sammaye
  • 40,888
  • 7
  • 90
  • 139
  • 4
    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. – Maurice Aug 29 '13 at 09:54
  • I've found that this approach does not work on iOS, at least for Safari/601.1 and Safari/602.1 – cronfy Aug 24 '17 at 10:21
  • Would be awesome to find out why, what browser is it in? – Sammaye Aug 24 '17 at 10:56
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
});
nick
  • 628
  • 8
  • 7
  • 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()... – Albert Català Jan 27 '16 at 02:51
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;
AllisonC
  • 2,790
  • 4
  • 26
  • 44
  • 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. – Kai Qing Jun 19 '19 at 22:38
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.

Chuck Conway
  • 15,795
  • 10
  • 56
  • 99
  • 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. – Nick Craver Oct 06 '10 at 21:38
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
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.

Ali
  • 2,071
  • 18
  • 13
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.

Dan D.
  • 67,516
  • 13
  • 93
  • 109
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" />
sebarmeli
  • 17,141
  • 6
  • 31
  • 39
-1

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

neoRiley
  • 437
  • 6
  • 10