Is my image loaded?
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.
- The image is in the DOM and loaded before the js executes
- The image may be in the DOM but not yet loaded
- The image
srcis 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
onloadoronerrorfor 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)
onerrorfor cached 404 pulls - cached 404s in FF/Opera show
img.complete == true - FF and SF3 support
img.naturalHeightandimg.naturalWidth, which report 0 if the image did not load (also cached 404 + img.complete) - Opera reports
img.widthandimg.heightof 0 for failed urls - All other browsers report
img.widthimg.heightas the dims of the alt text - IE reports
img.complete == falsefor 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
July 8, 2009 3:48 PM Farid
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?
July 8, 2009 5:46 PM Luke Smith
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.
July 21, 2009 11:21 AM David
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?
July 21, 2009 11:28 AM Luke Smith
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.
July 22, 2009 12:38 PM David
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
November 22, 2009 9:16 PM raynmune
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.
November 29, 2009 11:06 AM Luke Smith
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.