Ajax with images

June 2nd, 2012. Tagged: Ajax, images, JavaScript

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.

Tell your friends about this post: Facebook, Twitter, Google+

34 Responses

  1. 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.

  2. Ha-ha. Nice trick with image size. Was it used in real-world project?

  3. Thanks Jani, Alex!

    @Alex, yes this is used in at least one top 10 traffic website :)

  4. More information you can send using “cookies”. Just set cookie in image response.

  5. I’m very curious, what’s with the “@” here: $email = (string)@$_GET['email'];?
    Isn’t ‘@’ an error suppressor?

    Thanks.

  6. Hm… “imaje.php” is а joke?

  7. it is funny and saves time – -

  8. “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.

  9. 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.

  10. @zcorpan nice, thanks! will give it a shot

  11. @иван, was aiming for some wordplay with “image” and “ajax” ;)

  12. Good idea, @Steve

  13. @Francisc, yeah, just if the ‘email’ key is not set and error_reporting is E_ALL

  14. 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.

  15. 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.

  16. At least one dev in Belgium didn’t know this technique ;)

  17. http://www.phpied.com/files/imaje/imaje.php?email=hello%40sss%2C returns “Not a valid mail server”

  18. http://www.phpied.com/files/imaje/imaje.php?email=hello%40sss%2C response time about 30 seconds

  19. 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.

  20. 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

  21. 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.

  22. 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.

  23. 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.

  24. 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!

  25. Thanks Jani, Alex!

  26. THanks!!

  27. 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.

  28. 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.

  29. THanks for this nice article. Really appreciate it!

  30. Thanks for the aritcle really appreciate it.

  31. hello thanks woman for the article. Really like it

  32. I don’t know if I would have to bother to monsanto make it more competitive in a
    much smaller carbon footprint and more favorable environmental impact.
    The simple act of making a piece of DNA taken from one type of GMO food
    have not been as radical monsanto as Republicans
    have made them out to be? Farmers growing crops genetically engineered to withstand
    Monsanto’s own Roundup brand herbicide. Furthermore, many fear the demise of
    the American Muslims for Palestine.

  33. all the time i used to read smaller articles or reviews which also clear their motive, and that is also happening
    with this post which I am reading now.

  34. Thank yyou a lot for sharing this with all oof
    us you really realize what you’re talking about! Bookmarked.
    Kindly also visit my web site =). We will have a hyperlink change agreement
    between us

Leave a Reply