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.

3

6 Answers 6

67

Handle the <img> element's onerror event.

Sign up to request clarification or add additional context in comments.

6 Comments

@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: 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,
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.
Code examples for this: maisonbisson.com/post/12150/…
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 <img> returns a 404, but the response includes an image (a placeholder image, for example), the error event will not be fired.
|
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/

Comments

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

5 Comments

This raises a good point. You (OP) need to load the images with random querystring parameters.
The link died. This answer should be amended to contain content or have it’s green tick taken away.
KISS (aka Keep It Super Simple)
I would change the isType function to a simple typeof cfg.x==='function'. It is more readable than 'f', and also faster - see here: jsperf.com/typeof-variations
This doesn't work if request returns HTTP 404 and fallback image
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);

2 Comments

This assumes that all failed loads are 404's.
OP specifically said 404.
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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.