Is it possible to programmatically detect size limit for data url?
Asked Answered
H

1

11

I'm using javascript and html canvas to resize jpeg images. After resizing, I use canvas.toDataURL as the href attribute in an anchor tag in order to provide a link where users can download the resized images.

This works nicely up to a certain image size.

It seems that different browsers have different limits on the size of data urls as mentioned here: https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs

In chrome, when I'm over the data url size limit, nothing happens when I click on the download link; no errors or anything (as far as I can tell).

Is there a some way to programmatically detect whether a data url is too large? Maybe some browser event that will show me whether clicking a download link failed?

Ideally, I'd like to detect whether the download was successful. When data urls are too large, I'd like to display an explanation to the end user describing how to right click on the image and choose "save as ...", which always seems to work.

Update 1:

It looks like using canvas.toBlob is the best workaround for now. Here's the api documentation: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob.

Here's a jsfiddle that demonstrates how anchor href download links fail when using toDataURL for larger canvases, but toBlob seems to work:

https://jsfiddle.net/upgradingdave/c76q34ac/3/

Here are some related stackoverflow questions: canvas.toDataURL() download size limit

Data protocol URL size limitations

Harned answered 28/4, 2016 at 14:45 Comment(6)
So Chrome silently fails ... When Chrome fails to deliver an oversized data url, does console.log(theDataURL.length) report zero length or something else?Moats
Thanks for the idea. It looks like theDataURL.length returns the correct length even when the download fails, unfortunately.Harned
Simple curiosity, what was the length of the string? Also you may want to try to set the dataURI as the src of an img while listening to its error event. Not sure if it fires, since I never produced such too long dataURI, but if it doesn't I guess it's a bug. (Last time I tried to reach these limits, (without trying the download) I was hitting the maxLength of a string before the maxLength of a dataURI, are you sure it's not something like memory hit?)Cavern
Arf, after testing myself, I found that this limit would be only for <a> (the image loads fine but I get an uncatchable "Network Error" when using the download). What I would do is to play the safety by always converting your dataURI to a blob (or even better call canvas.toBlob() when available) and set the anchor's href to an objectURL. You shouldn't have this limitations. (I just tried with a 126MB output file and it works)Cavern
@Kaiido, yes that's the same behavior I'm seeing. I take a look at canvas.toBlob(). Thanks!Harned
Hi @Kaiido, please see my updates, I think using toBlob is the best workaround. I added a jsfiddle. If you want to add an answer suggesting to use toBlob, I'll mark it as accepted. Thanks again.Harned
C
12

No, there doesn't seem to be any event letting you know if an anchor with the download attribute actually succeeded to download the resource.

But, the limitation you are facing seems to only concern this situation : an anchor element <a> with the download attribute.

The browser can handle way longer dataURI (I think that in most browsers the limitation is the same as the one for strings length). E.g, it can load it to <img> element, and more importantly in your case, it can process the dataURI string.

This is important because it allows you to convert this dataURI to a blob, then to create an object URL from this blob. object URL's URI are small and don't face this length limitation in anchors with download attribute.

So the best in your case is probably to directly use the canvas.toBlob method (a polyfill is available on mdn), and to pass the object URL's URI as the href of your anchor.

var img = new Image();
img.crossOrigin = "anonymous";

img.onload = function(){
  var ctx = document.createElement('canvas').getContext('2d');
  ctx.canvas.width = this.width*10;
  ctx.canvas.height = this.height*10;
  ctx.drawImage(this,0,0,ctx.canvas.width, ctx.canvas.height);
  // convert our canvas to a png blob 
  //  (for type use the second parameter, third is for quality if type is image/jpeg or image/webp)
  ctx.canvas.toBlob(function(blob){
     myAnchor.href = URL.createObjectURL(blob);
    });
  // since we are dealing with use files, we absolutely need to revoke the ObjectURL as soon as possible
  var revokeURL = function(){
    // but we have to wait that the browser actually prepared the file to download
    requestAnimationFrame(function(){
      // we've waited one frame, it's seems to be enough
      URL.revokeObjectURL(this.href);
      this.href=null;
      });
    this.removeEventListener('click', revokeURL);
    };

  myAnchor.addEventListener('click', revokeURL);
  };

img.src = 'http://lorempixel.com/200/200/';
<a id="myAnchor" download="anAwesomeImage.png">download</a>

[ Live Demo ] (since download attribute is blocked by some UA in SE-Snippets)

But note that even if the string representation (URI) of the object URL is short, it will actually take the size of your file in browser's memory, and so until you hard-refresh the page (clearing cache), or close the tab where you created this object URL. So you'll absolutely need to call URL.revokeObjectURL() to clear this space.
But, since there is no event to know if the downloading actually succeeded, you're stuck with the onclick event, which will fire before the file downloading occurred. From my tests, waiting for a single frame with requestAnimationFrame is enough, but I may be wrong.


And for the ones coming here with an other source than a canvas for their dataURI, there are already a lot of posts in SO about converting a dataURI to a blob, and you can just check the mdn polyfill provided above, they're doing it too.

Cavern answered 30/4, 2016 at 5:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.