talideon.com

Blackout Ireland

February 22, 2005 at 4:24PM Detecting broken images with JavaScript

I hit an annoying problem with a project at work. It’s not my fault, there’s nothing wrong with the code, but sometimes things will happen: people will delete images out of the backend, not upload the image they’ve given, or will be referencing an image on another server. Welcome to a plague of broken images.

So how to cope with this? Your guess is as good as mine, but for now my solution is to pretend the image isn’t there at all. Not completely mind you: it occupies a space, but it’s not shown.

But it’s not immediately obvious how you check if the images on the page have loaded ok or not. After a few minutes of hacking, here’s the most transparent method I could come up with:

function IsImageOk(img) {
    // During the onload event, IE correctly identifies any images that
    // weren't downloaded as not complete. Others should too. Gecko-based
    // browsers act like NS4 in that they report this incorrectly.
    if (!img.complete) {
        return false;
    }

    // However, they do have two very useful properties: naturalWidth and
    // naturalHeight. These give the true size of the image. If it failed
    // to load, either of these should be zero.
    if (typeof img.naturalWidth != "undefined" && img.naturalWidth == 0) {
        return false;
    }

    // No other way of checking: assume it's ok.
    return true;
}

There’s half the work done. Now for the other half. When the page loads, we run over the images[] array to check them all.

AddEvent(window, "load", function() {
    for (var i = 0; i < document.images.length; i++) {
        if (!IsImageOk(document.images[i])) {
            document.images[i].style.visibility = "hidden";
        }
    }
});

And that should do the trick.

BTW, I’m experimenting with how I’d add syntax highlighting to my text parser. No code as yet, just messing with a few layout schemes right now. The markup feels rather nasty right now.

Comments

1 On March 2, 2005 at 15:57, Revence 27 wrote:

What if the image was meant to be as small as the placeholder? As in, it’s say, a logo? That’s why I don’t enjoy being good for non-Explorers. IE is filled with the sanity that’s rare elsewhere.

2 On March 6, 2005 at 21:02, Keith wrote:

Makes no difference. This is to prevent the ugly missing image graphic from appearing. This isn’t a matter of browser, just a matter of not giving the wrong idea to the user viewing the site: sometimes the broken images happen just because something went wrong during the transmission that’s rendered it undisplayible or something.

And IE has lots of problems of it’s own: it is, believe me from years of developing stuff for it, filled with its own kinds of insanity. All browsers do, and IE is no exception. The whole Gecko-based browser (Firefox, for instance) thing with the complete attribute is just a simple bug and not a terribly important one. It may even be that IE is the one that’s misbehaving and Gecko is behaving properly: I may have misinterpreted the DOM specification on this one. So be it. However, writing off other browsers because they don’t work like IE is hardly the most enlightened of attitudes.

3 On April 3, 2005 at 16:52, Kam wrote:

I’ve been using the IE variant of the script since IE supports ImageLoadFailed. Nice to see someone take a crack at a Firefox version, my new browser of choice. Thanks for the code. Would you mind pointing out where to put both the sections you outlined? ie. the exact code, I suppose they go in the head section or in a js file? Also what would be the proper syntax to call this function?

Many thanks.

4 On April 10, 2005 at 3:35, Mike H wrote:

I second Kam’s request.

5 On April 11, 2005 at 8:06, Revence 27 wrote:

But why don’t you just use the fact that a broken image won’t fire the onLoad? I mean, you let whoever loads register, and you send the grimReaper() to collect whoever didn’t heed the prophet’s advice! Not that this code isn’t the sexiest I’ve ever seen, but that that method won’t require going around any quirks.

6 On April 13, 2005 at 22:14, Keith wrote:

Wow! I didn’t think something this simple would get so much of a response! Ok. This weekend, I’ll code up a full example of it being used. But I’m not doing anything until after I’ve gone to see The Go! Team.

7 On April 13, 2005 at 22:21, Keith wrote:

And I don’t use that because it would entail installing handlers on all the images. That’s a lot of closures wasting a lot of memory for no good reason. As well as that, it’s only possible to install all those handlers after the page has loaded by which time the images will have pooped out anyway.

And even if they didn’t, doing it in one shot after is just as fast, if not faster. Also, we want to detect when the images haven’t loaded, not if they have, so putting it on the image’s onload event isn’t exactly optimal. onerror would be better.

You’ll always have breakages, and you’ll always have to use workaround for different thinks. Nowadays it’s not so bad, but you should have seen some of the hoops I used to have to jump through before the even paltry amount of the DOM that’s been implemented became common.

8 On April 14, 2005 at 8:27, Revence 27 wrote:

Oh, yeah. Now I see. I had tested it on a pilot page. And you know how misleading pilot pages can be. I’ll implement yours.

9 On March 15, 2006 at 15:05, Dismissed as Drone wrote:

This is a great idea. It might make sense to check the height or width attribute and, if set, update the src to point to a 1x1 transparent gif image (or a dynamic image stating that the image is missing), and then only hiding the images without dimensions.

10 On March 16, 2006 at 1:13, Keith wrote:

I’d need to check that, but I’m pretty sure that if the tag is given a width and height in attributes, they’ll be set regardless of whether the image loaded or not.

11 On April 12, 2006 at 19:43, Iair Salem wrote:

Your simple script it’s great and I will include it in my personal lib.js. It’s also well explained. Thank You Very Much! (and Also a special thanks to Google that always helps me finding whatever I want)

12 On May 22, 2006 at 3:27, David Millar wrote:

Say I wanted to use a replacement image instead of hiding broken images. I could just change document.images[i].style.visibility = “hidden”; to document.images[i].src = “useritem.gif”; right? Or am I totally confused?

13 On May 22, 2006 at 4:19, Keith wrote:

Pretty much. That should work just fine. In fact, if you encapsulated the code like so:

function CheckForBrokenImages(callback) {
    AddEvent(window, "load", function() {
        for (var i = 0; i < document.images.length; i++) {
            if (!IsImageOk(document.images[i])) {
                callback(document.images[i]);
            }
        }
    });
}

And call that function like this:

CheckForBrokenImages(function(img) {
    img.src = 'useritem.gif';
});

You ought to get the effect you’re looking for.

14 On November 21, 2006 at 14:32, Henrik Spang-Hanssen wrote:

Thanks, this code was just what I was looking for. I needed to check whether all images of a page had been downloaded before reloading the page. It seems different browsers uses different ways of html parsing the pages in terms of images being downloaded. That’s why I needed this function. Nice site you have got here.

15 On November 21, 2006 at 14:49, Henrik Spang-Hanssen wrote:

Thanks, this code was just what I was looking for. I needed to check whether all images of a page had been downloaded before reloading the page. It seems different browsers uses different ways of html parsing the pages in terms of images being downloaded. That’s why I needed this function. Nice site you have got here.

16 On November 27, 2006 at 5:02, Nelson Castillo wrote:

The onload event doesn’t work for me. Using your workaround I managed to get this to work.

http://wiki.freaks-unidos.net/emqbit/js/gallery.js

It seems the callback gets called RIGHT AWAY, even after the img.src = URL assignation. I tested it (with an alert) before the assignation.

http://wiki.freaks-unidos.net/emqbit/js/gallery.js

Thanks for this workaround.

17 On June 17, 2007 at 3:06, Aaron Bassett wrote:

I know I’m a bit late on this one but I came across your code and am using it in a little project. But I changed it slightly to make it a bit more elegant/compact and thought you might be interested. You can see the post about it here: http://foobr.co.uk/2007/06/detectbrokenimages/

basically I reduced it down to: return (!img.complete) ? false : !(typeof img.naturalWidth != “undefined” && img.naturalWidth == 0);

18 On June 18, 2007 at 12:55, Keith wrote:

Aaron, I value clarity and succinctness over terseness, which is why I wrote it the way I did. [smile]

19 On June 18, 2007 at 13:24, Aaron Bassett wrote:

Keith, your contact form seems to have an error so will pop my email in here saying as it directly related to this post anyways :)

sorry if you thought I was being cruel about your code, I do understand why it was written as it was and I think it is a perfect solution, hence why I am using it - I just ‘compacted’ it as I can still understand it perfectly and to me it doesn’t lose any readability :)

I wrote a reply to your comment hopefully clearing things up a bit over at http://foobr.co.uk/2007/06/detectbrokenimages/

20 On June 18, 2007 at 16:19, Keith wrote:

D’oh! Gotta fix that problem with the mail form.

Don’t worry, I wasn’t upset and didn’t think you were being cruel to my code or anything. It’s just that I wouldn’t compact that particular piece of code like that because the way it does what it does is rather obscure and non-obvious, so the reasons why it does what it does are something I feel ought to be made blatantly clear, hence the reason why it’s written the way it is.

I’m quite comfortable with the old ternary operator, I just use it rather judiciously.