Download Canvas API-Generated Images Using toBlob

For Purple11, a side project of mine built with Gatsby, I created a simple cloud texture generator that uses SVG filters and the Canvas API under the hood. The SVG filter part allowed me to easily create the cloud texture effect itself, thanks in great part to the feTurbulence filter, but I’ll keep the fascinating topic of SVG filters for another article.

In order to let a user download the generated texture to their machine as a Jpeg file, the inline SVG (with filters applied) first needs to be drawn on a canvas object, because unfortunately there’s no way to generate a downloadable file directly from an inline SVG graphic. Obviously, you can skip the whole SVG to Canvas part directly to the meat of the matter if you’re here just to know how to download an image that’s been drawn directly using the Canvas API with toBlob.

If you're new to the Canvas API in general and would like to get an overview, check out this article.

Inline SVG to Canvas

First, you’ll want to use a DOM method to select your inline SVG element. Say our SVG has an id of my-svg:

const mySVG = document.getElementById('my-svg');

And then you’d use XMLSerializer to serialize the content of the SVG and btoa (binary to ascii) to create a base64 version of it:

const xml = new XMLSerializer().serializeToString(mySVG);
const svg64 = btoa(xml);

You could then generate a downloadable file straight from the base64-encoded string, but the problem I faced is that most browsers impose a limit on the size of such base64-encoded string to be downloaded as a file. So I had to pile on from that point to go around that limitation.

Create a new HTML Image element and set its src property to the base64 version of our SVG:

const image = new Image();
const b64Start = 'data:image/svg+xml;base64,';
image.src = b64Start + svg64;

And now that we have an actual HTML image element on the page with a mirror image of our SVG, we can draw it onto a Canvas 2D context.

First, we need to create a Canvas 2D context to the size of our image. Assuming that you already have a canvas element on the page with an id of my-canvas:

const canvas = document.getElementById('my-canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, image.naturalWidth, image.naturalHeight);

And we can listen to the onload event on the image element we’ve created to actually draw it on the our canvas context object:

image.onload = () => {
  ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight);
};

Alright, so now with all of those shenanigans in place we finally have our inline-SVG result drawn into a canvas object. We can move on to the fun part and actually generate a downloadable file, thanks to canvas’ toBlob method.

Creating a Downloadable JPEG or PNG Image using toBlob

As it’s name implies, the toBlob method turns a canvas-drawn image into a blob. A blob is a binary representation of a string, which can then be downloaded as a file on your machine.

toBlob first takes a callback function as argument, which receives the blob itself, and in the callback you can proceed with doing whatever you’d like with the blob. toBlob can also 2 additional arguments, the mime type (defaults to image/png) and the quality, which expects a number between 0 (0% quality) and 1 (100% quality) that becomes useful when using a lossy mime type like image/jpeg.

For example, this will create a Jpeg blob at 90% quality:

canvas.toBlob(
  blob => {
    // do something with the blob here...
  },
  'image/jpeg',
  0.9,
);

URL.createObjectURL

What we can do is create an URL from the now in-memory blob using URL.createObjectURL.

We could just select an anchor tag on the page and set its href attribute to the URL:

canvas.toBlob(
  blob => {
    const anchor = document.getElementById('download-link');
    anchor.href = URL.createObjectURL(blob);
  },
  'image/jpeg',
  0.9,
);

Then the user would just have to click on the anchor to initiate the file download.


But we can do better and have the file download start automatically, so we’ll use a little trick where we programmatically create an anchor element (a), and programmatically click on it to automagically trigger the download:

canvas.toBlob(
  blob => {
    const anchor = document.createElement('a');
    anchor.download = 'my-file-name.jpg'; // optional, but you can give the file a name
    anchor.href = URL.createObjectURL(blob);

    anchor.click(); // ✨ magic!

    URL.revokeObjectURL(anchor.href); // remove it from memory and save on memory! 😎
  },
  'image/jpeg',
  0.9,
);

You’ll notice too that with our auto-downloaded image we can release/clean-up the generated blob to URL mapping from memory because the blob has now been downloaded as a file on the machine so we are done with it.

Wrapping Up

Oof, this might seem like a lot of juggling around to get what we need, but this is the kind of setup you’d do once and then get back to focusing on the actual features of your fancy pants app. Plus, once what we want saved is drawn into a canvas context, the toBlob method makes it quite easy to allow a file to be saved in whatever format and quality fits the needs of your app best.

Good luck! 🌈

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#