Is my image loaded?

By Luke Smith on November 5, 2008 10:33 PM

While developing on YUI's Slider widget for the upcoming 3.0 version, I came across a challenge regarding the slider's thumb image. In some cases, I need to reference offset dimensions for Slider to use to correctly place the thumb on the background rail.

There were three scenarios that I needed to address.

  1. The image is in the DOM and loaded before the js executes
  2. The image may be in the DOM but not yet loaded
  3. The image src is incorrect either in the DOM or provided via configuration

All A-grade browsers support the DOM events onload and onerror if the image hasn't already been loaded. That's the easy part. The hard part is dealing with the browser differences for successful and unsuccessful image urls that may or may not have been requested in the past.

What I found is listed here:

  • All browsers fire onload or onerror for images that haven't received a server/cache response
  • FF and Opera cache the 404 response from bad images, and treat future requests for that url as a cache pull
  • FF and Opera browsers do not fire (at least) onerror for cached 404 pulls
  • cached 404s in FF/Opera show img.complete == true
  • FF and SF3 support img.naturalHeight and img.naturalWidth, which report 0 if the image did not load (also cached 404 + img.complete)
  • Opera reports img.width and img.height of 0 for failed urls
  • All other browsers report img.width img.height as the dims of the alt text
  • IE reports img.complete == false for failed urls

Some of these are suppositions, and likely there's a lot more to it than what I've found. But at any rate, it …"inspired" me to write this little function to attach some callbacks to an <img> element (or generate one on the fly) that simulate an onload and onerror mechanism, that will execute when the image is loaded or determined to be in error. If the image is a cache pull, the callbacks execute immediately.

// 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;
}

Some example usage might be

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

And a demo here.

If I missed something, please let me know. Otherwise, I hope you find this useful.

7 Comments

  1. Gravatar

    Thanks for the great information. I have implemented and tested this in a couple of browsers, and it works everywhere (Firefox, IE, Chrome) except for Opera 9.64 (the version that I have currently installed). Any clue?

  2. Gravatar

    Can you email me a link to the page or to a gist with the relevant code? I'd be happy to help you get to the bottom of it.

  3. Gravatar

    Is there any similar function to try and load an html file (not an image)? or is there any way to request a html file as an image so your solution can apply?

  4. Gravatar

    If you're looking to load an html page within another page, you're looking for the iframe element. It has its own events (and issues). This solution doesn't relate to that. If you're looking for loading of remote content as an html snippet, use XHR (via a proxy for remote data). There's plenty of documentation on both of these solutions on the web.

    You can't load html data from an image tag.

  5. Gravatar

    Thanks for the response,
    Maybe I should explain my question a little bit more:


    1) I won't be able to use XHR (Ajax) because the request is being made in a different domain that the html file resides (cross domain security).
    2) What I basically want to do is that inside www.example1.com, I'd like to try to load sample.html from www.example2.com. If the html file loads successfully (I don't need to use its content, just the fact that the url exists is fine for me) I will set a js variable to true, if it doesn't load successfully, I'll set that js variable to false.
    3) From your response above, I think I can try to dynamically create an iframe and set its src to that sample.html. Then I should check if the ifram loads successfully or not. etc...


    Thanks

  6. Gravatar

    hello luke. Interesting solution.
    A little problem with chromium 4.0.247.0 (Ubuntu build 31906). In the next code:

    var img=image('test.jpg',{
    success : function () { alert(this.width); },
    failure : function () { alert('Damn your eyes!'); }
    });
    alert(img.width);

    the last alert shows 0. Its important to say that last alert pops first. I tried it on FF3+ and IE6 and works good. Seems like CHR doesnt wait until function image() finishes. What do you think? is CHR doing things wrong?

    Thanks for your time.

  7. Gravatar

    Interesting. I don't have an Ubuntu env to test on, I haven't looked at Chromium on any OS. It typically behaves as WebKit does, which is well represented by Safari--at least insofar as a stable release.

    It could be that is a Chrome bug. I thought Chr 3 was the latest, making 4 a dev build? If so, it's fair to expect instability/inconsistency.

    All this said, this code is due for a rewrite because the IE completeness test should be using readyState. This functionality may also be encapsulated in a YUI 3 module (some day), which would be preferable to this solution, IMO.

Your comment?

HTML is ok

No TrackBacks (http://lucassmith.name/mt/mt-tb.cgi/115)

ls.n

LucasSmith.name

Luke and Liam

I'm Luke. I am a front end engineer at Yahoo! on the YUI team.

Mostly I write about code stuff, but occassionally I'll mix in some real life. You've been warned.

Archives

Tags

Feeds

Subscribe to feed Recent entries

Content licensed under Creative Commons

Code licensed under BSD license

©2005 - 2010 Lucas Smith

Powered by Movable Type