0

So I have an <img> tag on my page.

<img id="the_image" src="/imageServlet">

Let's assume that the /imageServlet is responding with a few HTTP response headers that indicate that the browser should never cache this image. The reason, in this case, is that the image may contain sensitive employee information and there are laws in certain countries that the browser should not (without user consent) save the image on the client's hard drive in case the machine is stolen. I believe the response header is Cache-Control: no-cache

All I really want to do is duplicate this image tag, but I do not want another HTTP request to be made for this image.

My application is a single page, using javascript I dynamically these image tags - but now I would like to display this same image in more than one place. For example the image could be a thumbnail version of a user's profile picture. In one place I display the thumbnail as a small list, and then I display the same image in a popup when you click that user.

Every time I create a new <img> tag with this src, the browser is making a fresh call to the server to retrieve the image. I don't care if the user presses "refresh" on the browser and a new HTTP request for that image should load - but within a single page load, shouldn't the Image data be available in memory, so couldn't I reuse this?

How can I get around this? This greatly impacts the performance of the page, where images are reused and displayed in multiple places, and they need to reload every time (even though user has not navigated to any other page or refresh browser).

Here is some code that I wrote just now with an idea where any time the application wants to load an image it would call "loadImage" which would eventually call the callback with some HTML element that it can freely use. The idea would be this central function would create the Image but it would make sure that for any unique src only one single HTTP request would be made - regardless if the HTTP response headers contain Cache-Control: no-cache or not.

(function(){
  var HANDLERS = {};

  /**
   * I want to keep around a cached "Image" then the "callback" will be called with a
   * duplicate somehow - whatever comes back from here can be inserted somewhere, but
   * hopefully no additional HTTP requests needs to be made!!
   * @param {Image} img
   * @return {Image} A copy of the incoming img
   */
  function duplicateImage(img) {
    // Does not work!! D'oh, is this even possible to achieve??
    return img.cloneNode(true);
  }

  /**
   * Users can call this to load the image. The callback is called when the image is ready.
   * In case the image fails the callback will be given an object that contains a success flag.
   * 
   * Example:
   * loadImage('/imageServlet?xxxx', function(result) {
   *   if (result.success) { $(body).append(result.image); }
   *   else { console.log("The image failed to load!") }
   * });
   * 
   * @param {string} src The src for the image
   * @param {function({success:boolean, image:Image})} callback Give a callback to be called when ready
   */
  window.loadImage = function(src, callback) {
    if (!HANDLERS[src]) {
      var queue = [];

      // This loadImage can be called more than once with the same src
      // before the image has successfully loaded. We will queue up any
      // callbacks and call them later, then replace this with function
      // that will directly invoke the callback later.
      HANDLERS[src] = function(callback) {
        queue.push(callback);
      }

      // Create the image here, but do it only once!!
      var el = new Image();

      // When the image loads, we will keep it around in the el
      // variable, but the callback will be called with a copy
      el.onload = function() {
        el.onload = el.onerror = null;
        var call = HANDLERS[src] = function(callback) {
          callback({
            success: true,
            image: duplicateImage(el) // This is where the image is duplicated!
          });
        }
        for (var i=0; i<queue.length; i++) {
          call(queue[i]);
        }
      }

      // This is just in case the image fails to load. Call any
      // queued callbacks with the success false.
      el.onerror = function() {
        el.onload = el.onerror = null;
        var call = HANDLERS[src] = function(callback) {
          callback({success: false});
        }
        for (var i=0; i<queue.length; i++) {
          call(queue[i]);
        }
      }
      el.src = src;
    }
    HANDLERS[src](callback);
  }
})();
codefactor
  • 1,616
  • 2
  • 18
  • 41

2 Answers2

3

Try an ajax call when the page has started to get the image data, and assign that to the "src" of the images you want to "copy" that data to.

$.ajax({
    type: "GET",
    url: 'some url',
    datatype:"image/jpg",
        success: function (data) {
            $('#img1').attr('src', url.createObjectURL(data));
            $('#img2').attr('src', url.createObjectURL(data));
        }
});

Because this writes the acutal image into the "src" feild, rather than a link, you can at a later time copy this image from one control to another without more requests to the server very simply:

$('#img3').attr('src', $('#img2').attr('src'));

Non jQuery version

function myAjax() {
    var xmlHttp = new XMLHttpRequest();
    var url="some url";
    xmlHttp.open("GET", url, true);
    xmlHttp.setRequestHeader("Content-type", "image/jpg");
    xmlHttp.setRequestHeader("Connection", "close");
    xmlHttp.onreadystatechange = function() {
        if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            document.getElementById('img1').setAttribute('src', xmlHttp.responseBinary);
        }
    }
    xmlHttp.send();
}
binderbound
  • 791
  • 1
  • 7
  • 27
  • I am going to try this out; however, supposing it does work, it would only work if the image's src is using the same domain as the page I am currently using. In my case, though, it would be the same domain but it would not be a general solution for any src. – codefactor Oct 22 '13 at 04:46
  • I think there is something missing with your code. I tried to set the `src` attribute of the image like `$('#img1').attr('src',data)` and it did not work. – codefactor Oct 22 '13 at 05:03
  • Huh, that's a shame. have you tried the other one? `document.getElementById('img1').setAttribute`? Also, make sure jQuery is actually selecting your image properly - if it has hold of nothing it will just silently fail. – binderbound Oct 23 '13 at 00:20
  • made some changes to both versions – binderbound Oct 23 '13 at 00:35
  • Incidentally, I think jQuery does allow cross-domain requests. – binderbound Oct 24 '13 at 00:42
  • Jquery does allow jsonp but cross domain security checks cannot be surpassed by a library. Jsonp would not be able to get the binary of an image file. – codefactor Oct 24 '13 at 02:10
0

You may want to store file name("src") in an array so that javascript can determine if the image was loaded. You can use canvas to render the image...once the image was loaded into the canvas, you can clone this image using toDataURL()...no need to fetch from server.

StaleMartyr
  • 778
  • 5
  • 18
  • Canvas may work for FF/Chrome/Safari - but not on IE. Over 90% of our users use IE. – codefactor Oct 22 '13 at 05:20
  • Oh sorry...Using php..you can convert image into base64...and store it in an array in client's browser. To render an image, you'll just have to use. – StaleMartyr Oct 22 '13 at 05:31
  • Do you know how to create the "somebase64string" in javascript? Supposing I have some response data from ajax call? I tried this: http://stackoverflow.com/questions/13908019/display-image-in-src-tag-with-response-text-not-base64 - but it didn't work – codefactor Oct 22 '13 at 05:33
  • It is a series of string that can be rendered as image..its well...too long to include. this is how..in PHP: $base64 variable will now hold the base64 data. – StaleMartyr Oct 22 '13 at 05:54
  • You can use ajax to get the base64-rized image :-) Good luck – StaleMartyr Oct 22 '13 at 05:58