Resources
What & Link | Type |
---|---|
MDN - Canvas APIs     - Element: HTMLCanvasElement |
Guide |
Jenkov: HTML Canvas Tutorials | Guide |
Canvas Libraries / Abstractions
Since low-level canvas work can take a lot of boilerplate code and requires some very specific knowledge, many find it helpful to use a library that abstracts working with the Canvas, and provides wrapper methods for things like drawing, manipulating, or even exporting Canvas data.
- Fabric.js (NPM Package)
- Very powerful, but also not super abstracted - you still have to build your own controls, setup handlers, etc.
- I'm sure there are others, but I don't have an abbreviated list to share.
Common Tasks
Clear the Canvas
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
Inserting an SVG Onto the Canvas
The easiest way is to use the context.drawImage()
method:
<canvas id="canvas"></canvas>
<img id="svgAsImg" src="./file.svg" />
<script>
const context = canvas.getContext('2d');
const image = document.getElementById('svgAsImg');
const renderImage = () => {
context.drawImage(image, 0, 0, image.width, image.height);
}
if (image.complete) {
renderImage();
} else {
image.addEventListener('load', renderImage);
}
</script>
🚨 But, to use this you first need to get the SVG as an img
element (the svgAsImg
), because drawImage
only works with images, not SVG / DOM.
How to Import an Existing SVG Element
If you have the SVG as an existing <svg>
element in the DOM, you can use this approach to convert it to an image element.
<svg id="mySvg">...</svg>
<script>
const context = canvas.getContext('2d');
const svgElem = document.getElementById('mySvg');
// Construct Data URI
const svgDataUri = URL.createObjectURL(new Blob([svgElem.outerHTML], {type: 'image/svg+xml'}));
const image = document.createElement('img');
image.addEventListener('load', () => {
context.drawImage(image, 0, 0, image.width, image.height);
URL.revokeObjectURL(svgDataUri);
});
// Trigger load
image.src = svgDataUri;
</script>
Pre-Formatting as Data-URIs Powered Images
If you already have the full SVG as a string, but for some reason can't save and import it as a file, you can pre-make your <img>
tags, with <img src='data:image/svg+xml;utf8,<svg ... > ... </svg>'>
. You just need to encode the data first though. You can use an online tool, like this one, or do it all in JS:
const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600"><defs/><path fill="#fff" d="M-1-1h802v602H-1z"/><g><circle cx="401" cy="292.6" r="250.5" fill="#fff" stroke="#000" stroke-width="1.5"/><path fill="#fff" fill-opacity="null" stroke="#000" stroke-opacity="null" stroke-width="1.5" d="M254 171h108v58H254zM411 169h145v67H411zM365 289h35v37h-35zM256 391h174v87H256z"/><path fill="none" stroke="#000" stroke-linecap="null" stroke-linejoin="null" stroke-opacity="null" stroke-width="1.5" d="M302 108l48 44M474 95l-57 54M270 390l35 38M324 392l-21 35M338 392l16 31M381 391l-25 31M309 478l21-30M349 477l-16-27M372 477l10-28M408 477l-25-28"/></g></svg>`;
const img = document.createElement('img');
img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgStr)}`;
// You can use `img` without appending, or go ahead and append to show in browser
document.body.appendChild(img);
You can also use a library dedicated to this (with more bells and whistles, plus fixes some security issues): canvg.
Insert another canvas into your canvas
The context.drawImage()
method actually accepts HTMLCanvasElement
as the image to insert! Meaning that you can totally just insert the contents of one canvas element into another!
📄 -> MDN Doc on drawImage()
Capturing / Exporting the Canvas
Because the Canvas deals with more raw image data, getting the data back out is actually both possible and relatively easy.
There are multiple ways to do so.
If you want the canvas exported as an image (perhaps a PNG
), you will likely want to use the canvas.toBlob()
method, which even lets you specify the desired mimeType
(such as image/png
) and image quality. Once you have the blob, you can base64 it, display it as an image, provide it as a download to the user, etc.
🚨 Warning: If you pass a mimeType that is unsupported by the browser, this method will not throw any exceptions, but will simply fallback to the default of
image/png
. Do not trust (without checking) that it will be able to fulfill anything other thanimage/png
.
Related function: toDataURL
If you want the raw pixel data, there is the methodcontext.getImageData
, which returns data containing a raw Array buffer object. Here is an example of it being used to convert a Canvas to BMP
in a browser that normally does not support that mimeType for export.
Troubleshooting / Common Issues
- Changes to
context.font
are being ignored / overwritten- Canvas will reset the font to default if the dimensions of the canvas are altered (S/O)
context.drawImage()
works for inserting an SVG in Chrome, but not in Firefox!- There is a known bug / quirk with FF: you must provide explicit dimensions within SVG strings
- For example, if all your SVG has for dimensions is
viewBox="0.0 0.0 440.0 50.0"
, you also need to addwidth="440" height="50"
for it to work with insertion in FF. The values also cannot be percentages.
- For example, if all your SVG has for dimensions is
- There is a known bug / quirk with FF: you must provide explicit dimensions within SVG strings
- Canvas is drawing everything in the wrong spot, things are appearing warped, circles / arcs are showing up as ovals, etc.
- If you are trying to make the canvas responsive with CSS (for example, so it fills the screen, as a background), make sure you are also updating the
width
andheight
properties of the<canvas>
element- This is important, because canvas operations use the fixed dimensions of the canvas element as reference points; not the rendered / computed dimensions
- To dynamically update the canvas, you could use a
resize
listener:const canvasElement = document.querySelector('canvas'); window.addEventListener('resize', () => { const dimensions = canvasElement.getBoundingClientRect(); canvasElement.width = dimensions.width; canvasElement.height = dimensions.height; });
- If you are trying to make the canvas responsive with CSS (for example, so it fills the screen, as a background), make sure you are also updating the
- Canvas is not completely filling shapes / slicing off parts of shape when using
.fill()
- If you using
.fill()
after drawing lines, make sure that you are calling.moveTo()
to set the first point of the path before you do anything like.arc()
.
- If you using