Using jQuery to Load Alternate Images

On March 16, 2009, in Javascript, by Anuj Gakhar

We can use the onerror event of an img tag to load an alternate image if the main image is not found. Something like this :-

<img src="my_image.jpg" onerror="this.src='offline.jpg'" />

The above HTML code tries to load my_image.jpg and if not found, loads offline.jpg as an alternate. This is pretty good because this means you don’t have to do any server-side checks for file existence which can be really heavy if there are 50+ or so images on the page. This is all good, except that this code wont be treated as valid XHTML. Apparently, onerror is deprecated. So, what to do?

The solution is to not use onerror in the <img> tag but to add the onerror event to the <img> via Javascript.  And jQuery is the best option I think for this job. Here is what I came up with :-


// wait for page to load
$(document).ready(function() {
// add fixBroken code to all images on the document
$('img').fixBroken();
});

$.fn.fixBroken = function(){
return this.each(function(){
var tag = $(this);
var alt_img = 'offline.jpg';
tag.error(function() { // this adds the onerror event to images
tag.attr("src",alt_img); // change the src attribute of the image
return true;
} );
});
};

This code only runs after the page has loaded and adds the onerror event to all img tags on the document. And the page would validate as XHTML as well.

Does anyone have a better solution to this little issue ?

Tagged with:  

19 Responses to Using jQuery to Load Alternate Images

  1. You do not want to use $(document).ready() to run this–since it runs *before* all the images are loaded.

    Instead you want to use $(window).load()–which is the same as window.onload.

    The window.onload event doesn’t run until all images have loaded (or at least been requested and failed.)

  2. Seb Duggan says:

    Dan – surely that’s exactly why you do want to use $(document).ready()…

    Before it even starts loading the images, the page will know what to do in the event of an error. Otherwise, you may get broken images showing before the window.load() event fires.

  3. Sorry, I should have paid more attention to the code. I glanced at it quickly.

    However, there’s still a potential problem with this code.

    The timing of when $(document).ready() fires isn’t an exact science. There’s no way to guarantee it fires *before* images load. This leaves it open to weird bugs.

    A quick search turned up this solution:

    $(window).load(function() {
    $(‘img’).each(function() {
    if (!this.complete || typeof this.naturalWidth == “undefined” || this.naturalWidth == 0) {
    // image was broken, replace with your new image
    this.src = ‘http://www.tranism.com/weblog/images/broken_ipod.gif’;
    }
    });
    });

    The idea they’re using is to check the “complete” property which should be set to true/false if the image loaded. However, since Gecko doesn’t support that it falls back to check naturalWidth (a Gecko property) to see if it’s not defined or set to an invalid size (0).

  4. Anuj Gakhar says:

    @Dan, that sounds like a decent solution to the problem. I like the idea of using complete and naturalWidth to determine if the image is loaded or not. However, I am not sure if this is any better than onerror event as that is an internal event and I had no problems with onerror in any browser.

  5. @Anuj:

    The problem isn’t with wanting to use the onerror–which is the correct event to monitor if an image loaded or not.

    The problem is that $(document).ready() is not guaranteed to fire before all images have tried to load.

    Where I can see there being issues is if you’re running some other jQuery plug-ins during onready that are running before the fixBroken() plugin.

    I just seem to recall someone trying to implement a similar fix to yours, but was having some issues because his code was run after the browser had attempted to load the images.

  6. Anuj Gakhar says:

    @Dan, Thanks for the explanation. I will keep that in mind and if I notice anything different, I will post here. Cheers.

  7. Rolf says:

    Forgive my ignorance when it comes to javascript, but what is the optimal code now?
    And where should I place it, in the head section I suppose? Or can I put it in my external .js doc and if so, can I call the doc in the usual way or is a special command needed for this code?
    Thanks in advance for your time.

  8. Anuj Gakhar says:

    @Rolf, this script would be placed after you have loaded the jQuery library and its ok to place it in the head section or an external .js file. It will only run when the document is loaded.

  9. Peter says:

    You could also forget all the JavaScript and go the CSS way:

    IMG { background-image: url(‘offline.jpg’); }

    Though this might give weird results if you use GIF or PNG images with transparent parts.

  10. Denis says:

    @Peter: That would seem to work but would it not then load that extra image for every image broken or not? This might seem like a small issue as it’s just one error image but if you have different error images and or a complex page redraw time etc would be effected heavily?

  11. Peter says:

    @Denis:

    It will actually load an extra image for EVERY image in the document, not only for the broken ones.

    However, images are cached quite efficiently (with default settings), so the offline.jpg will only be downloaded once for your whole site, even if the user revisits a few days later.

    I would guess the resource usage in the browser would also be minimal, since the browser knows it’s the same image being reused everywhere.

    I would definitely guess the resource usage is (far) less using one CSS command (which is handled internally by the browser) than looping through all the images in the document using JavaScript.

  12. Nagaraj Hubli says:

    @Peter with the css fix, wouldn’t the IE browser still show the “red x” for the broken images?

  13. Peter says:

    Good point. I haven’t tested it, but from memory I recall that it will display the red X in the top left corner, on top of the background image. The same goes for other browsers also.

  14. [...] it’s not added to an element inline. I did a bit more research and – inspired by this article – opted to use Javascript to add the onerror event to the required elements once the DOM had [...]

  15. Robson Pinto says:

    This solution is perfect! Join idea of Anuj and Dans:

    $.fn.broken = function(img){
    return this.each(function(){
    if(!this.complete || typeof this.naturalWidth == ‘undefined’ || this.naturalWidth == 0) this.src = img;
    });
    };

    $(‘img’).broken(‘http://www.tranism.com/weblog/images/broken_ipod.gif’);

  16. bk says:

    Rob/Anuj/Dans – not 100% sure but I think the naturalWidth detection for Firefox is wrong as it causes all images in IE to be marked as broken because this.naturalWidth == ‘undefined’ is always true in IE. This seems to work in all browsers instead:

    $.fn.broken = function(img){
    return this.each(function(){
    if(!this.complete || (typeof this.naturalWidth != ‘undefined’ && this.naturalWidth == 0))
    this.src = img;
    });
    };

    $(window).load(function () {
    $(‘#whatever’).broken(‘/whatever.jpg’);
    });

  17. Ashish Chotalia says:

    You can do like this as well:

    $(document).ready(function(){
    $(“img”).each(function(index) {
    $(this).error(function() {
    $(this).unbind(“error”).attr(“src”, “/images/NoImage.png”); // If you want to replace with any blank image.

    $(this).hide();//You can simply Hide it using this.
    });
    $(this).attr(“src”, $(this).attr(“src”));
    });
    });

    More info here

Leave a Reply

© 2011 Anuj Gakhar
%d bloggers like this: