Ajax with images
So you can do Ajaxy stuff with XMLHttpRequest or or iframes or dynamic JavaScript tags or... how about simple images. This is best used for simple stuff where you can have a limited number of predefined responses, such as "success" and "oops".
All you do is create an image and set its source and this makes a request:
new Image().src = "mystuff.php";
This is if you don't care about the response. If you want to inspect the response though you can attach onload and onerror handlers:
var i = new Image(); i.onload = function() { // inspect the response }; i.src = "mystuff.php";
If you can assume you'll have "OK" response most of the time, you can have the .php return a 204 No Response which is the smallest response (no body). If the .php determines there's something wrong, it then can return an image.
When you send a 204 response, the onerror handler will be called because the response is not really an image. It looks backwards to have your success handler be called onerror, but if you expect more successes than errors, then it's probably worth it.
var i = new Image(); i.onload = function() { // an error occurred }; i.onerror = function() { // success! }; i.src = "mystuff.php";
And the final thing - if you want to have coded responses, in other words be able to differentiate between different errors (each with its error code), you can have the .php return different image sizes. Say with constant height but varying width. E.g. 1x1 image, 2x1, 3x1 and so on. In the onload you inspect the size of the image and determine the type of response.
var i = new Image(); i.onload = function() { // an error occurred if (i.width === 1) { // error #1 } if (i.width === 7) { // error #7 } // etc... }; i.onerror = function() { // success! }; i.src = "mystuff.php";
I'm using a different errors as an example, but you can always have it the other way around: you consider onload a suceess and there are different types of successes.
Email address validation example
Let's take a look at a little more practical example. Let's validate email addresses on the server.
We'll return 7 different image sizes if the supplied email address is invalid or a 204 response is the email looks fine.
The OK response:
function ok() {
header("HTTP/1.0 204 No Content");
}
The error response that generates an image with a desired width and height of 1 px:
function image($width) { header("Content-Type: image/png"); $im = imagecreate($width, 1); imagecolorallocate($im, 0, 0, 0); imagepng($im); imagedestroy($im); die(); }
The error codes:
// 1x1 = empty input // 2x1 = missing @ // 3x1 = too many @s // 4x1 = missing username // 5x1 = missing host // 6x1 = blocked // 7x1 = no dns record for host // 204 = OK
And, finally, the "business" logic:
$email = (string)@$_GET['email']; if (!$email) { image(1); } // split to username and domain $splits = explode('@', $email); if (count($splits) === 1) { image(2); } if (count($splits) !== 2) { image(3); } list($user, $host) = $splits; if (!$user) { image(4); } if (!$host) { image(5); } $blocked = array('yahoo.com', 'gmail.com', 'hotmail.com'); if (in_array($host, $blocked)) { image(6); } if (!checkdnsrr($host)) { image(7); } // all fine, 204 ok();
Test page
You can play with the test page here:
http://www.phpied.com/files/imaje/test.html
The markup:
<input id="i"><button id="b">check email</button><pre id="r">enter an email</pre>
And the JS that makes a requests and checks for ok/error:
var i; $('b').onclick = function() { i = new Image(); i.onerror = function() { $('r').innerHTML = "thanks!"; }; i.onload = function() { $('r').innerHTML = "invalid email address!"; }; i.src = "imaje.php?email=" + encodeURIComponent($('i').value); $('r').innerHTML = "checking..."; };
All there is to it!
Verbose page
A more verbose test can inspect the width of the returned image and display an appropriate message to the user.
Play with it here:
http://www.phpied.com/files/imaje/verbose.html
var i; $('b').onclick = function() { i = new Image(); i.onerror = ok; i.onload = function() { err(i.width); } i.src = "imaje.php?email=" + encodeURIComponent($('i').value); $('r').innerHTML = "checking..."; }; function ok() { $('r').innerHTML = "this is one valid email address, good for you!"; } function err(num) { var errs = [ '', 'Seriously, I need an email', "Where's the @?", "Too many @s", "Missing username", "Missing mail host", "BLOCKED! Go away!", "Not a valid mail server", ]; $('r').innerHTML = errs[num]; }
A good side effect of using a global i is that async responses don't mess up the result. E.g. you send requests #1 and #2, #2 arrives first and is OK, #1 arrives later and is an error. At this point you've overwritten i and the handlers for #1 are no longer available. Dunno is it's possible but it would be cool to be able to further abort a previous request if you've made one after it.
Thanks
Thanks for reading! I know it's hardly new for you, my two faithful readers, but these responses with varying image size came up in a conversation recently and as it turns out there are rumors that there might be about 3 developers in Chibougamau, Quebec, that are not aware of this technique.
This entry was posted on Saturday, June 2nd, 2012 and is filed under Ajax, images, JavaScript. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Get notification for future posts: follow me on Twitter or subscribe to my RSS feed

June 2nd, 2012 at 3:50 am
Interesting approach with the response handling. I was already familiar with using images in cross-domain situations or as simple “beacons”, but never really thought about actually getting any data back. Never really occurred to use the size or response code to trigger behavior.
With the image size style response this might actually be a more feasible cross-domain two way communication method.
June 2nd, 2012 at 11:17 am
Ha-ha. Nice trick with image size. Was it used in real-world project?
June 2nd, 2012 at 11:28 pm
Thanks Jani, Alex!
@Alex, yes this is used in at least one top 10 traffic website
June 3rd, 2012 at 1:02 am
More information you can send using “cookies”. Just set cookie in image response.
June 3rd, 2012 at 6:49 am
I’m very curious, what’s with the “@” here: $email = (string)@$_GET['email'];?
Isn’t ‘@’ an error suppressor?
Thanks.
June 3rd, 2012 at 4:55 pm
Hm… “imaje.php” is а joke?
June 3rd, 2012 at 8:47 pm
it is funny and saves time – -
June 4th, 2012 at 2:58 am
“A good side effect of using a global i is that async responses don’t mess up the result. ”
It does, actually. You still get two image objects, each with their own onerror and onload handlers attached to them. The handlers should still be invoked even if you don’t have a reference to the image, and the browser should make sure not to garbage collect the image while it’s still loading and has listeners.
“Dunno is it’s possible but it would be cool to be able to further abort a previous request if you’ve made one after it.”
Just use the same image object (move “new Image()” to the global assignment). When you set src while the image is loading, the current load will be aborted.
June 4th, 2012 at 11:44 am
Neat trick, Stoyan. I’d be nervous about 204 triggering onerror across all browsers. (Would be nice if you ran a Browserscope user test on this.) It might be better to just return an error status code explicitly.
June 4th, 2012 at 11:31 pm
@zcorpan nice, thanks! will give it a shot
June 4th, 2012 at 11:33 pm
@иван, was aiming for some wordplay with “image” and “ajax”
June 4th, 2012 at 11:33 pm
Good idea, @Steve
June 4th, 2012 at 11:34 pm
@Francisc, yeah, just if the ‘email’ key is not set and error_reporting is E_ALL
June 5th, 2012 at 1:03 am
Nice, creative technique Stoyan.
I agree with Steve about the stability of 204 as onerror-> ok message.
I don’t like numbered errors without text, they’re useless without their lookup table. I’d go with this for simple ok/error responses only, seems to be a good balance between ease of use vs. Code readability.
June 5th, 2012 at 8:56 am
Hey Stoyan, don’t use a 204 & onerror to note success. Best to just use a different image size.
We tested this technique out when building the latency test for boomerang. onerror also fires when there’s a network error (DNS error, TCP timeout, etc.), so you can’t trust it for a success notice. Secondly, 204 on some older webkit mobile browsers will not fire any events.
I documented this somewhat here: http://tech.bluesmoon.info/2010/01/bandwidth-test-v11.html
Also, inside the handlers, I believe you can use `this` to refer to the current image object if you care about it. I’d avoid using a global variable called `i` because that variable name tends to be very common across code.
June 5th, 2012 at 10:14 am
At least one dev in Belgium didn’t know this technique
June 6th, 2012 at 1:12 am
http://www.phpied.com/files/imaje/imaje.php?email=hello%40sss%2C returns “Not a valid mail server”
June 6th, 2012 at 1:14 am
http://www.phpied.com/files/imaje/imaje.php?email=hello%40sss%2C response time about 30 seconds
June 11th, 2012 at 4:41 pm
I did a sorta similar thing some time ago – it was actually first released the day after 9/11 so I guess people had their attention focused elsewhere. No wonder it took so long to get the 800km from Brampton to Chibougamau!
http://www.ashleyit.com/rs/rslite/
I used a timer and polling rather than img.onload at the time because various browsers didn’t support it well, however the code still works after 11 years so there’s something to be said for that.
June 11th, 2012 at 4:55 pm
If you’re interested in other ways to get data back and forth, I haven’t updated it in 5 years, but here is a list of all sorts of transport layer techniques from a talk I used to give at Ajax conferences.
http://www.ashleyit.com/ajax/transports.htm#%5B%5B-%20Transports%20in%20Detail%5D%5D
September 21st, 2012 at 2:20 pm
Here’s a version I did some years ago to transfer arbitrary data in a cross-domain manner:
http://lab.brainonfire.net/IDTP/index.html
The data has to be fixed-length so that a length “header” can be sent over, but that’s an implementation detail that could be changed without much work.
November 1st, 2012 at 4:33 am
Same issue with Binyamin here with http://www.phpied.com/files/imaje/imaje.php?email=hello%40sss%2C . It doesn’t respond and loads continuously for minutes. I’m sure my internet connection is very good since I download with 10gb/s. And no, I am not downloading anything while I try to connect to that link.
November 2nd, 2012 at 5:14 am
Lima, my internet is terrible and I had no problem with the link you posted. Maybe you should check your firewall settings, or connect your ISP.
November 7th, 2012 at 6:40 am
Hey!Thanks for the informations!You make it look so simple,but for me,as a young programmer,it’s not that easy.Keep up the good work!
December 13th, 2012 at 3:38 pm
Thanks Jani, Alex!
March 14th, 2013 at 1:26 am
THanks!!
March 14th, 2013 at 1:34 am
A disclaimer is generally any statement intended to specify or delimit the scope of rights and obligations that may be exercised and enforced by parties in a legally recognized relationship. In contrast to other terms for legally operative language, the term disclaimer usually implies situations that involve some level of uncertainty, waiver, or risk.
March 14th, 2013 at 2:38 am
Featured content represents the best that Wikipedia has to offer. These are the articles, pictures, and other contributions that showcase the polished result of the collaborative efforts that drive Wikipedia. All featured content undergoes a thorough review process to ensure that it meets the highest standards, and can serve as the best example of our end goals.
March 14th, 2013 at 11:57 pm
THanks for this nice article. Really appreciate it!
March 15th, 2013 at 12:02 am
Thanks for the aritcle really appreciate it.
March 19th, 2013 at 2:32 am
hello thanks woman for the article. Really like it