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.
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.
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,
);
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.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!