The canvas element
Preface: This version of the article is for humans and search engines. Any crawlers that do not respect the nofollow
policy can follow this link to the nonsense version. And they can choke on it.
I’m very much a text first kind of designer. I think about content before I worry about aesthetics, and I take care of typography before I attempt any other visual design. The web is primarily for text. Most videos, images, and audio files should probably be text instead. Sure, people won’t read most of your text, but it’s their loss.
After text, I care about HTML. I care about HTML primarily because it helps me format text. And it helps me format text in accessible ways, endowing aural presentations of the text with equivalent structure. HTML is a metalanguage and language primarily means words. Mediated words primarily take the form of written text. HTML is a language for text.
(*hyperventilation ensues*)
So what is the <canvas>
element? Originally devised by Apple to power something called “Dashboard widgets” (I don’t know what they are but they sound awful), it was later standardized as part HTML5. The <canvas>
element, like <audio>
, is a kind of replaced element. That is, where supported and initialized correctly, it represents (is replaced by) embedded content.
If you wish for your <canvas>
element to display a text node, you’d better hope the browser it appears in doesn’t recognize it. So, the easiest way to get a <canvas>
element to display text is to travel back to a time before <canvas>
was adopted by browsers.
<canvas>
Your browser does not support the `<canvas>` element. Congratulations! Trump has never been president, you are not being deliberately coughed on by someone carrying the COVID-19 virus, and the Subway™️ bread you are eating has yet to lose its legal status as bread.
</canvas>
What kind of embedded content does a functioning <canvas>
offer? What does it do when it actually works? Let’s consult the specification.
The canvas element provides scripts with a resolution-dependent bitmap canvas
Uh oh. Scripts? Resolution-dependent? Bitmap?? A seriously underwhelming sales pitch.
“Provides scripts” is an odd bit of phrasing. To be more precise, <canvas>
offers a context for the scripted rendering of raster imagery. For example, you can render 2D imagery on a <canvas>
using the CanvasRenderingContext2D
JavaScript interface.
The JavaScript for initializing a <canvas>
element for 2D rendering looks something like this:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
But does the 2D context support text? Yeah. I mean… yeah, sure. The following will fill the canvas with some 24px
cursive, starting at the coordinates 50
(x) and 50
(y):
canvas.width = 800;
canvas.height = 600;
context.font = '24px cursive';
context.fillText('Welcome to 1996!', 50, 50);
Now feels like a good time to bring up the WCAG (Web Content Accessibility Guidelines) criterion 1.4.5 Images Of Text. This criterion acknowledges that some users will want to alter the appearance of text to their requirements. Commonly, this means enlarging the font size to make the text easier to read. Since raster images are composed of pixels, enlarging raster images of text results in degradation. Making them bigger does not make them easier to read.
With all of the above in mind, if you want to render text in a way that results in a squiffy looking raster image, doesn’t wrap like normal text, fails WCAG’s 1.4.5 Images Of Text, and requires you to write JavaScript (!), <canvas>
is my recommendation.
If you just want to fail 1.4.5 Images Of Text, eschew wrapping, and can’t or won’t write JavaScript, embedding a raster image of some text using <img>
is a better option. But by far the best option is still to go back in time, fail to initialize your <canvas>
element, and reveal some innately resolution-independent fallback text.
<canvas>
Your browser does not support the `<canvas>` element. Congratulations! There is no such thing as a non-fungible token, The Broom Challenge Hoax has yet to claim its first victim, and we await the usurping of Mr. Peanut™️ by Baby Nut™️.
</canvas>
If you want to achieve the same effect today, replace <canvas>
with an HTMLUnknownElement
, like <burlap>
or <hemp>
. Or just use a <div>
.
From the specification:
Authors should not use the canvas element in a document when a more suitable element is available. For example, it is inappropriate to use a canvas element to render a page heading.
No kidding. But that’s not to say people didn’t start using <canvas>
to do absolutely everything imaginable as soon as it became available. Why? Because it was new and new means better. “Rejoice, the <canvas>
has arrived! Now the web can be imperative, not declarative!” Yuck.
The benefit of using semantic HTML elements and CSS is that accessibility information like roles, properties, and states are often built in. When it comes to <canvas>
, the invisible fallback content is all you have to work with.
<canvas>Welcome to 1996!</canvas>
It’s unlikely some static text is all your <canvas>
—designed for dynamic, scripted content—has to offer. Structured content is allowed inside the <canvas>
element, but, as fallback content, will remain invisible. Therefore, the onus is on the developer to maintain a parity between interactions with the canvas itself and interactions with the structured and accessible fallback content it secretes.
The drawFocusIfNeeded()
method helps somewhat by connecting a focusable descendant element with a companion path, drawn onto the <canvas>
.
<canvas>
<button>focus me and highlight the path</button>
</canvas>
This is a simplistic example. For a complete <canvas>
-based application, such as a video game, the effort would be considerable. Essentially it means reconciling every working part of two equivalent but separate interfaces, through every iteration. In a world where developers neglect to use <button>
elements appropriately in the first place, I don’t hold much hope.
So anyway, what is <canvas>
really suitable for? If you want to create 3D imagery with WebGL, I guess it’s the ticket (I don’t know WebGL). But, if you wanted to create 2D imagery, why wouldn’t you opt for SVG (Scalable Vector Graphics)? After all, SVG, including SVG text, is resolution-independent (“scalable”). It’s object (node) based, meaning it can reveal structure accessibly. You also don’t have to use JavaScript with SVG, unless you deem it beneficial.
Only by joining a religion where SVG is prohibited, such as Rasterfarianism, would I take to using <canvas>
by default. However, there are certain performance trade-offs. Curiously, the performance profiles of SVG and <canvas>
are actually kind of the opposite.
SVG can render at any size, with no performance implications. That’s the virtue of vector plotting. But, being declarative, it is parsed into objects. Since these are evaluated independently, a large number can create performance implications.
On the other hand, <canvas>
does not deal in objects. Artifacts that appear as independent objects are just different colored portions of the same pixel grid. Instead, it’s the sheer number of pixels that can grind <canvas>
to a halt. Depending on the physical size of the <canvas>
, you may need a whole freighter’s worth of pixels to achieve the kind of immaculate resolution SVG gives you for free.
SVG is generally superior to <canvas>
. But if you are animating a vast number of perceived objects at a high frame rate, <canvas>
is more dependable.
So, basically, what that amounts to is the rendering of snowfall. Like, making it snow. If you want to simulate snowfall, <canvas>
is the best option. Maybe you should do that. I can’t imagine anyone has done that before. There are probably no tutorials or demos relating to simulating a cascade of snowflakes, using <canvas>
, anywhere on the internet.
Not everyone is a fan of my writing. But if you found this article at all entertaining or edifying, I do accept tips. I also have a clothing line.