Why do canvas.toBlob and canvas.toDataURL have different return types
Asked Answered
L

1

5

canvas.toBlob() requires a callback function but canvas.toDataURL() returns the result synchronously. Why the difference? It's not a problem, but I'm super curious and I can't find an explanation for why it is that way.

Lashandralashar answered 9/3, 2019 at 5:21 Comment(6)
Yeah, it’s a bit weird, and probably Just How Things Ended Up. (Async is important here, though, because it lets the browser not block the UI during encoding.)Aldous
canvas.toDataURL() is much older, from before Blob() was standardized. It was realized in the interim that hi-res image and mobile performance would greatly benefit from a node.js-popularized asynch interface, which allows the host environment more flexibility in scheduling processing and handling RAM without stopping or slowing the page's execution. Virtually all Blob() interfaces are async.Interfile
@Interfile Can you post that as an answer so I can upvote it? Your answer sounds great to me and making it an answer would allow others to upvote it tooLashandralashar
@Interfile Virtually all Blob() interfaces are async except Blob constructor and any Blob methods?Meprobamate
@Kaiido: yes, interfaces where Blobs are used are async: FileReader, Fetch, etc...Interfile
@Interfile new Blob([blob1, blob2]) is synchronous, URL.createObjectURL is synchronous Blob.slice is synchronous, FileReaderSync is synchonous, xhr can send blob synchronously though it's a very bad idea etc. And anyway, Canvas.toBlob doesn't consume a Blob, it generates one, so the fact it is async is not related to the fact it returns a Blob.Meprobamate
M
13

Because toDataURL was an early error...

At the time it was implemented (by Safari IIRC), the FileAPI was still only being discussed and exporting the result of a canvas was already a need. So they made this method, which does return the data in a convenient data URL, that you can use directly as the src of several elements in the document. At this time, returning it synchronously sound like a good idea, everything in the Canvas API was synchronous.

But a few years later, with more and more implementations, more and more uses, and new APIs, it became obvious that toDataURL was not a good idea. Just like synchronous XHR if you are old enough to remember.
Even though you can have the data URL in a synchronous way, to display it will be an asynchronous task anyway.

To generate an image file from a canvas is a slow operation, you need to export all the pixel data, un-multiply it, and then call the compression algorithms.

Added to that, a data URL needs to be stored as a base64 encoded String, 34% bigger than the binary data it represents, and copied in memory every time you assign it somewhere in the DOM...

The FileAPI introduced ways to hold binary data in memory, and to be able to display it, manipulate it or send it as is to a server. All this implying minimal memory overhead => data URLs became obsolete (for most cases).

So it has been decided to add a new method, that would take advantage of these new APIs, and which would return a Blob instead of a data URL. In the common fight against UI blocking operations, it has been decided that this method would be asynchronous (, but unfortunately, that was before Promise came-in...). Now all that has to be done synchronously is to grab the pixel data, like getImageData does. The remaining operations can be done in-parallel.

Meprobamate answered 12/3, 2019 at 7:45 Comment(6)
That's all fine and dandy, until you run into other limitations browsers intentionally put on you, like triggering downloads must happen synchronously from a user click event. You throw an async call like toBlob into the mix and Safari will block you, for example. It's a good idea until it suddenly isn't.Applegate
@user2867288 On which platform do they need the download to be part of an user gesture? On macOs they clearly allow this operation from even an element that is not in the DOM. And user-gesture are supposed to live for a few ms anyways, is your canvas really that big it requires a few ms to be exported?Meprobamate
iOS. It's not about how long it takes, it's the fact that it's asynchronous even if it takes 1 millisecond, you lose the trust of the click event.Applegate
Well that's unexpected... Per specs there should be a "transient activation duration", and even though they don't define a lower limit, last I checked all UAs agreed to at a least a few ms.Meprobamate
Yeah. On your comment "is your canvas really that big it requires a few ms to be exported", even a 4k canvas takes a couple of seconds, not milliseconds to convert to a blob on a mid-range laptop. Specs shouldn't be written on arbitrary time-frames.Applegate
Well to be fair specs don't require an user-gesture for triggering a download through an a element. And I never seen a canvas take that long to export... On my laptop a 4K canvas full of noise take 120ms to export (both through toDataURL and through toBlob). Btw if we assume iOS has this very weird 1ms duration, when the synchronous toDataURL call is done, the user-activation would also be dead there since it actually takes the same time and they should check last activation time based on wall clock time, meaning you blocked the UI for nothing.Meprobamate

© 2022 - 2024 — McMap. All rights reserved.