22

I'm loading (large) images dynamically to draw into a html5 canvas, something like this:

var t = new Image();
t.onload = ...
t.src = 'http://myurl';

But every once in a while would like to cancel the image request completely.

The only way I came up with is setting src to ''. i.e.

t.src = ''

This works in many browsers, but it seems that only Firefox actually cancels the http request for the image.

I tested this with Fiddler2 by disabling caching and enabling "emulate modem speed". Then running a fiddle to test canceling a image request. (I'd love to hear other ideas on how to test this)

I know there are ways to cancel all requests (as in this question), but I'd like to only cancel one.

Any ideas on other ways (especially on mobile browsers) to do this?

Community
  • 1
  • 1
Amir
  • 4,131
  • 26
  • 36
  • 4
    Be wary of `t.src = ''`, the spec is vague on what should happen. See http://www.nczonline.net/blog/2009/11/30/empty-image-src-can-destroy-your-site/ – Crescent Fresh Feb 07 '11 at 20:55
  • 1
    Right... some of the browsers are making requests to page's url (or base url) to try to load the image with `src=''`. Under Firefox, setting `t.src=null` also cancels the request, but does nothing on others. The newer (current-work) spec, says (in some situations) that [it should fire onerror](http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#update-the-image-data)... kindof. – Amir Feb 07 '11 at 23:01
  • 3
    Setting `src` to an inline image under Firefox also cancels the previous request: `t.src='data:image/gif;base64,R0lGOD...` see it in [this fiddle](http://jsfiddle.net/amirshim/F4HbT/21/) – Amir Feb 07 '11 at 23:16
  • empty image src make the page reload the request that get you on the page. So when you arrive on a page after a post, the browser will silently redo the POST. This is really a good way to shoot yourself in a foot. – regilero Feb 11 '11 at 12:16

4 Answers4

15

This is the only way that I managed to get it to work in all modern browsers (Chrome, Safari, Mobile-Safari, Firefox, and IE9).

  • Load an empty and hidden iframe
  • append an image tag to the body in the iframe
  • when the img.onload completes, you can use that image dom element to draw to an html5 canvas with drawImage()
  • if you want to cancel the load, issue a stop() on the iframe's contentWindow (or execCommand("stop", false) on contentDocument in IE).
  • after you cancel a image load, you can reuse the iframe to load more images.

I created a class for this, and put the coffeescript code (and its compiled javascript) on github: Cancelable Html5 Image Loader

If you want to play with it, I also created a jsfiddle to test it out. Remember to start Fiddler2 (or something like it) to see that the actual network request is really being canceled.

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Amir
  • 4,131
  • 26
  • 36
  • +1 thanks for that hack. It would be nice to find a way to provide somthing for older IE version without canvas, maybe with the libs that add canvas support on IE. – regilero May 04 '11 at 20:09
  • @Amir,how can we implement this into openlayers tile requests ? – Myra Jan 04 '12 at 17:09
  • @Amir I got the same problem and tried to replicate your script but I can't get the iframe.contentWindow.stop() method to work. Is there any way you can take a look at it? – Ming May 07 '14 at 14:35
4

You can try window.stop() to stop all requests, but not individual ones. In IE, it's not window.stop() it's document.execCommand("Stop", false).

If you've got other stuff going on request-wise, then doing this will interfere with it. (You would follow-up with a re-request for resources you still want, but that is too much like hard work).


So if you wanted to stop a single request for a large image, what you could do is load the image into a document within a hidden IFRAME. The onload event of the IFRAME can be used to load the image into the main document, by which time it ought to be cached (presuming you have the caching directives configured to do so).

If the image is taking too long, then you can access the IFRAME's contentWindow and issue a stop command to that.

You need to have as many IFRAME elements as there are images that can be requested simultaneously.

Lee Kowalkowski
  • 11,591
  • 3
  • 40
  • 46
  • "but not individual ones"... can you back this up? The rest of the info you provided was already stated and linked to in the question. – Amir Feb 08 '11 at 22:52
  • You can only stop all the requests in progress for the window. Which gives me an idea, see additional info added to my answer! – Lee Kowalkowski Feb 11 '11 at 21:18
2

I doubt there's a cross-browser way to make this happen without hacks. One suggestion is to make an AJAX request to a server-side script which returns a Base64 encoded version of the image which you can then set as the src attribute. Then you can cancel this request if you decide to cancel loading the image. This could be difficult if you want to show the image progressively however.

Michael Mior
  • 28,107
  • 9
  • 89
  • 113
  • 1
    Great idea, but there are a couple of problems associated with this. First is that the amount of data transferred is 33% more. (base64: 24bits -> 32bits). Then (especially on mobile browsers), it puts lots of memory pressure on javascript, since the data has to be stored as a string in JS and then it also has an extra step of base64 decoding it. For large files (200K+) this could hurt. But this could definitely work well in some scenarios. – Amir Feb 11 '11 at 20:43
  • That's a good point. When I came up with the idea, I didn't think about the overhead. Apparently there has been some effort in reading binary files using AJAX http://nagoon97.wordpress.com/2008/04/06/reading-binary-files-using-ajax/. I've never tried this myself, but you could potential do this and then draw the image using a ``. – Michael Mior Feb 11 '11 at 22:19
2

I have same problem actually and made some tests.

I have made some tests with iframes and have some success. Tested on Firefox 3.6 and Chrome. For the tests I used 9 images of 15Mb. With img preloading I killed my firefox several times (yes, not much memoty on this computer), using img the "suspend" action does not prevent image loading if the request have started, with iframes the suspend action really stop the download (as I remove the iframe). Next step will be testing with XMLHttpRequests. As this version of my test code is using the braowser cache behaviour form the image url to prevent a second loading and this works as well with ajax requests. But maybe with iframes I could find a way to retrieve binary data from the loaded image in iframe directly (with limitation on same domain urls).

This is my test code (really fast on Chrome, may kill your firefox with too much very big images):

// jQuery.imageload.shared.list contains an array of oversized images url
jQuery.each(imglist,function(i,imgsrc) {

    name = 'imageload-frame';
    id = name + "-" + i;
    // with img it is not possible to suspend, the get is performed even after remove
    //var loader = jQuery('<img />').attr('name', name).attr('id',id);
    var loader = jQuery('<iframe />').attr('name', name).attr('id',id); 

    //no cache on GET query taken from jQuery core
    // as we really want to download each image for these tests
    var ts = +new Date;
    // try replacing _= if it is there
    var ret = imgsrc.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
    // if nothing was replaced, add timestamp to the end
    imgsrc = imgsrc + ((ret == imgsrc) ? (imgsrc.match(/\?/) ? "&" : "?") + "_=" + ts : "");

    loader.css('display', 'none').appendTo('body');
    loader.after( jQuery('<a>')
            .text(' stop ')
            .attr('href','#')
            .click(function(){
                loader.remove();
                jQuery(this).text(' suspended ');
            })
    );

    // start the load - preload
    loader.attr('src',imgsrc);

    // when preload is done we provide a way to get img in document
    loader.load(function() {
        jQuery(this).next("a:first")
            .text(" retrieve ").unbind('click')
            .click(function() {
                var finalimg = jQuery('<img />');
                // browser cache should help us now
                // but there's maybe a way to get it direclty from the iframe
                finalimg.attr('src',loader[0].src);
                finalimg.appendTo('body');
            });
    });
});

edit and here is fillde to test it: http://jsfiddle.net/wPr3x/

regilero
  • 29,806
  • 6
  • 60
  • 99
  • So close, but there's a problem. For some reason, when the image is loaded in the iframe, it's not immediately available in the cache. so the image ends up being loaded twice. here's a fiddle to play with. http://jsfiddle.net/amirshim/na3UA/ Start fiddler2 (on a pc) and see how the image is being requested twice. If you turn `use_unique_name` off then you'll see that the image is being cached after the first time the entire page is loaded. – Amir Feb 12 '11 at 22:48
  • I created a new question to try to solve that problem: http://stackoverflow.com/questions/4981229/image-not-loading-from-cache-after-its-loaded-in-an-iframe – Amir Feb 12 '11 at 23:15
  • on th fiddle example I don't see where you set the load() callback before setting the src attribute and after inserting the iframe. So you may have this load event called just after the load of "about:blank" or such, i.e. the empty iframe base source. – regilero Feb 13 '11 at 00:17
  • I do `loader.onload =` instead of the jquery `load()`. But here's a fiddle with the jQuery methods which has the same problem: http://jsfiddle.net/amirshim/na3UA/1/ – Amir Feb 13 '11 at 06:20
  • as anwered in the other question I think it can work if you write it like me :-) . Here's a fiddle to test it, but be carefull to change images url sometimes, to prevent DOS on targeted servers. http://jsfiddle.net/wPr3x/ – regilero Feb 14 '11 at 12:36