65

After a user uploads a file we have to do some additional processing with the images such as resizing and upload to S3. This can take up to 10 extra seconds. Obviously we do this in a background. However, we want to show the user the result page immediately and simply show spinners in place until the images arrive in their permanent home on s3.

I'm looking for a way to detect that a certain image failed to load correctly (404) in a cross browser way. If that happens, we want to use JS to show a spinner in it's place and reload the image every few seconds until it can be successfully loaded from s3.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Tobias Lütke
  • 1,028
  • 1
  • 9
  • 9
  • 1
    IIRC, there is an onerror event for images... http://www.devguru.com/technologies/javascript/10916.asp – scunliffe Jun 10 '10 at 22:48
  • 2
    See http://stackoverflow.com/questions/92720/jquery-javascript-to-replace-broken-images – Gert Grenander Jun 10 '10 at 22:51
  • 1
    @Dorathoto why the bounty? The most upvoted answer is the way to go: https://stackoverflow.com/questions/3019077/detecting-an-image-404-in-javascript#answer-3019099 – Guerric P Apr 29 '21 at 17:23

6 Answers6

68

Handle the <img> element's onerror event.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 10
    @xal: Just be sure to hook the `error` event *before* you set the `src` property (if you're doing this with `new Image` or `document.createElement("img")` rather than via an HTML string). Otherwise, you're in a race condition and the event could fire before you start handling it (just like the `load` event). – T.J. Crowder Jun 10 '10 at 22:52
  • 1
    @T.J: Not necessarily. Javascript runs on the UI thread, so if you handle the event immediately after setting `src`, there shouldn't be any risk. However, you're still right, – SLaks Jun 10 '10 at 23:03
  • 9
    Javascript runs on the Javascript thread, which on *some browsers* is also the UI thread. It is not necessarily the download thread. I've helped people fix bugs caused by precisely this, setting `src` before setting a `load` handler. There's a race condition there. If you already have the handler registered, the event handling code will put it on the execution stack to be run once the JS thread is available to do something else. If you don't, you can miss the event. – T.J. Crowder Jun 11 '10 at 06:51
  • 1
    Code examples for this: http://maisonbisson.com/post/12150/detecting-broken-images-in-javascript/ – Lukas Jan 20 '16 at 13:58
  • 18
    I'd like to comment on this, despite it being somewhat dated at this point, simply because it's a pretty popular question and answer. If an `` returns a `404`, but the response includes an image (a placeholder image, for example), the `error` event will **not** be fired. – Tyler Roper Oct 17 '18 at 16:30
39

First option:

<img src="picture1.gif" onerror="this.onerror=null;this.src='missing.gif';"/>

Second option:

<html>
<head>
<script type="text/javascript">
    function ImgError(source){
        source.src = "/noimage.gif";
        source.onerror = "";
        return true;
    }
</script>
</head>
<body>
    <img src="image_example1.jpg" onerror="ImgError(this)" />
</body>
</html>

PS: it's pure javascript! you don't need any libraries. (Vanilla JS)

Example in Fidler

https://jsfiddle.net/dorathoto/87wd6rjb/1/

Dorathoto
  • 201
  • 7
  • 17
12

From: http://lucassmith.name/2008/11/is-my-image-loaded.html

// First a couple helper functions
function $(id) {
    return !id || id.nodeType === 1 ? id : document.getElementById(id);
}
function isType(o,t) {    return (typeof o).indexOf(t.charAt(0).toLowerCase()) === 0;}

// Here's the meat and potatoes
function image(src,cfg) {    var img, prop, target;
    cfg = cfg || (isType(src,'o') ? src : {});

    img = $(src);
    if (img) {
        src = cfg.src || img.src;
    } else {
        img = document.createElement('img');
        src = src || cfg.src;
    }

    if (!src) {
        return null;
    }

    prop = isType(img.naturalWidth,'u') ? 'width' : 'naturalWidth';
    img.alt = cfg.alt || img.alt;

    // Add the image and insert if requested (must be on DOM to load or
    // pull from cache)
    img.src = src;

    target = $(cfg.target);
    if (target) {
        target.insertBefore(img, $(cfg.insertBefore) || null);
    }

    // Loaded?
    if (img.complete) {
        if (img[prop]) {
            if (isType(cfg.success,'f')) {
                cfg.success.call(img);
            }
        } else {
            if (isType(cfg.failure,'f')) {
                cfg.failure.call(img);
            }
        }
    } else {
        if (isType(cfg.success,'f')) {
            img.onload = cfg.success;
        }
        if (isType(cfg.failure,'f')) {
            img.onerror = cfg.failure;
        }
    }

    return img;
}

And here how to use it:

image('imgId',{
    success : function () { alert(this.width); },
    failure : function () { alert('Damn your eyes!'); },
});

image('http://somedomain.com/image/typooed_url.jpg', {
    success : function () {...},
    failure : function () {...},
    target : 'myContainerId',
    insertBefore : 'someChildOfmyContainerId'
});
j0k
  • 22,600
  • 28
  • 79
  • 90
unomi
  • 2,642
  • 18
  • 19
11

just bind the attr trigger on the error event.

$(myimgvar).bind('error',function(ev){
    //error has been thrown
    $(this).attr('src','/path/to/no-artwork-available.jpg');
}).attr('src',urlvar);
oasisfleeting
  • 111
  • 1
  • 2
4

I just did

if ($('#img')[0].naturalWidth > 0) {

as i noticed there was no naturalWidth if the image 404'd.

However, i can understand wanting to use a method above.

Adam Mac
  • 99
  • 3
3

This worked for me (mine is in coffeescript). You'll need to replace with a spinner instead, of course.

checkImages = ->
  $("img").each ->
    $(this).error ->
      $(this).attr("src", "../default/image.jpg")

$(document).on('page:load', checkImages)

I'm guessing the javascript equivalent is something like

function checkImages() {
  $("img").each(function() {
    $(this).error(function() {
      $(this).attr("src", "../default/image.jpg");
    });
  });
};

$(document).on("page:load", checkImages);

dav1dhunt
  • 179
  • 1
  • 10