Joshua's Docs - HTML Canvas Notes and Tricks

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 than image/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 add width="440" height="50" for it to work with insertion in FF. The values also cannot be percentages.
  • 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 and height 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;
      });
  • 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().
Markdown Source Last Updated:
Tue May 07 2024 03:31:25 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Fri Nov 13 2020 18:17:28 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback