Command Palette

Search for a command to run...

Hectal
PHASE 5Intermediate ~21 min· topic 7 of 7

Topic 5.7

Canvas, a First Look

In one line

<canvas> is a blank rectangle of pixels that JavaScript paints on; it's fast for games, charts and image effects, but what you draw is only pixels, so you must give it a text alternative.

Think of it like this

A whiteboard versus fridge magnets. SVG is like magnets: every shape is a separate object you can move later. Canvas is a whiteboard: once you draw a circle, it's just ink. To move it, you wipe the board and draw everything again in the new place.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

canvas
An HTML element that is a blank grid of pixels JavaScript can draw on.
Bitmap
An image stored as a grid of pixels.
Context
The object you get from getContext("2d"); it has all the drawing commands.
Immediate mode
Drawing that becomes pixels at once; the shapes aren't remembered as objects.
Retained mode
Drawing where every shape stays an object you can change later (like SVG elements).
Radians
A way to measure angles for drawing. A full circle is 2 × π (about 6.28).
Fallback content
Content inside <canvas> used when the canvas can't be shown, and as a text alternative.

Step by step

01Add a canvas with fallback

Give it a pixel size and an id. The text inside describes what will be drawn, for anyone who can't see it.

index.htmlwhole filehtml
<canvas id="chart" width="300" height="150" role="img"
        aria-label="Bar chart: apples 40, bananas 70, cherries 25">
  Fruit sold this week: apples 40, bananas 70, cherries 25.
</canvas>

02Get the context

Nothing appears until JavaScript draws. getContext("2d") returns the drawing object; it's the same object every time you ask.

app.jswhole filejavascript
const canvas = document.getElementById("chart");
const ctx = canvas.getContext("2d");

03Draw rectangles

Set the colour, then fill. Each bar is a rectangle whose top-left y is the canvas height minus the bar's height, because y grows downwards.

app.jswhole filejavascript
const data = [40, 70, 25];
ctx.fillStyle = "steelblue";
data.forEach((value, i) => {
  ctx.fillRect(30 + i * 90, 150 - value * 2, 60, value * 2);
});
Draw rectanglesdiagram
Rendering diagram…

04Draw a circle and text

Circles are paths: begin, add an arc, fill. Text uses a CSS-style font string.

app.jswhole filejavascript
ctx.beginPath();
ctx.arc(270, 25, 15, 0, Math.PI * 2);
ctx.fillStyle = "gold";
ctx.fill();

ctx.fillStyle = "black";
ctx.font = "14px sans-serif";
ctx.fillText("Fruit", 10, 20);

05To change it, redraw it

You can't select the gold circle and move it. You clear the canvas with clearRect and draw everything again. Animations do this up to 60 or more times a second with requestAnimationFrame (JavaScript course).

app.jswhole filejavascript
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ...draw everything again in its new position

Try it yourself

  1. 1

    Change the sky

    In the first example, predict the first console line if you move the sun to arc(150, 40, …). (The pixel at 240,40 becomes sky: 135,206,235,255.) Run it to check.

  2. 2

    Add a fourth bar

    In the chart example, add a table row for Dates with 55. You don't need to touch the JavaScript: predict the console line, then run it. (Hint: the fourth bar starts at x = 300, off the edge. Make the canvas wider to see it.)

  3. 3

    Fix the blur

    In the third example, change the blurry canvas's attributes to width="300" height="150" and watch it become sharp.

Code & diagrams

Your first canvas drawing New tab

`gold` is rgb(255, 215, 0) and `seagreen` is rgb(46, 139, 87). The only way to ask "what's here?" is to read pixels. The fallback text is a text node, not an element, so `children` is 0.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Pixel at the sun's centre (r,g,b,alpha): 255,215,0,255
Pixel at the bottom-left (the hill): 46,139,87,255
Shapes in the DOM: 0 (the drawing is only pixels)
A bar chart with a real text alternative New tab

The numbers live in a real HTML table (hidden visually, still read by screen readers), and the canvas is drawn from it. One source of truth, two ways to perceive it.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Drew 3 bars from the table: [ 40, 70, 25 ]
Blurry canvas: attribute size vs CSS size New tab

The top canvas has a third of the pixels, so the browser stretches each one three times wider: soft text and fuzzy curves.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

blurry: bitmap 100x50, shown 300x150
sharp: bitmap 300x150, shown 300x150

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Draw before the canvas exists

Put <script>document.getElementById("c").getContext("2d")</script> in the <head>, above the <canvas>.

terminal
$ Console
── what you'll see ──
Uncaught TypeError: Cannot read properties of null (reading 'getContext')

Break #2

Size the canvas only with CSS

Write <canvas id="c"></canvas> and canvas { width: 600px; height: 300px; }, then draw a circle at 300,150.

terminal
$ the page shows
── what you'll see ──
The circle is missing (it's off the 300 × 150 bitmap), and everything else looks blurry and twice as big.

Myth vs fact

Myth

Canvas and SVG do the same thing.

Fact

Both draw graphics, but SVG keeps shapes as styleable, accessible elements, while canvas only keeps pixels. Use SVG for icons and diagrams, canvas for many moving things or pixel effects.

Myth

Text drawn on a canvas can be read by screen readers and search engines.

Fact

It's only pixels. Put the information in fallback content, aria-label, or real HTML next to the canvas.

Myth

A canvas needs a library to work.

Fact

The built-in 2D context draws rectangles, paths, text and images. Libraries such as Chart.js add conveniences on top of it.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    For crisp drawing on high-DPR screens: canvas.width = cssWidth * devicePixelRatio; canvas.height = cssHeight * devicePixelRatio; ctx.scale(devicePixelRatio, devicePixelRatio); and keep the CSS size at cssWidth × cssHeight.

  • ▸

    Drawing an image from another origin without CORS taints the canvas: from then on, getImageData() and toDataURL() throw a SecurityError, so a page can't use canvas to read pixels of images it isn't allowed to see.

  • ▸

    getContext() also accepts "webgl", "webgl2" and "webgpu" for GPU-accelerated 3D and compute. OffscreenCanvas lets a Web Worker draw off the main thread, keeping the page responsive.

  • ▸

    Focusable elements placed in canvas fallback content (like buttons) are part of the accessibility tree and keyboard order, so you can offer real controls for things drawn on the canvas.

Remember this

  1. 1

    <canvas width="300" height="150"> creates a bitmap: a grid of pixels, 300 × 150 by default if you don't give a size. On its own, HTML can't draw anything on it. The canvas needs JavaScript: you ask it for a drawing context, an object full of drawing commands, with canvas.getContext("2d"), and call methods like fillRect, arc and fillText.

  2. 2

    Canvas is immediate mode: each command paints pixels immediately and is forgotten. There is no tree of shapes (unlike SVG's retained mode, where every shape stays an element in the DOM). So the browser can't tell you which shape was clicked, CSS can't style a shape, and screen readers see nothing but a picture. In exchange, drawing thousands of things every frame is fast, which is why games, data visualisations with many points, and photo filters use canvas.

  3. 3

    The 2D context draws with a current style: set ctx.fillStyle = "tomato" (any CSS colour), then ctx.fillRect(x, y, width, height) fills a rectangle. For other shapes you build a path: ctx.beginPath(), then ctx.arc(x, y, radius, startAngle, endAngle) (angles in radians: a full circle is Math.PI * 2), then ctx.fill() or ctx.stroke(). ctx.fillText("Hi", x, y) draws text. Like SVG, y grows downwards from the top-left.

  4. 4

    The width and height attributes set how many pixels the bitmap has. CSS width/height only stretch that bitmap on screen. If they disagree, the drawing is scaled and looks blurry or squashed. On high-density screens (DPR 2+), professionals make the bitmap DPR times bigger and scale the context, so lines stay crisp.

  5. 5

    Accessibility fallback. Content between <canvas> and </canvas> is fallback content: it isn't drawn in browsers that support canvas, but it stays in the page and assistive technology may use it, so it's the place for a text description, a data table, or interactive equivalents. For a simple picture, also add role="img" and an aria-label describing it. For a chart, give the numbers in a table too; a picture of a chart isn't enough for a screen reader user.

Explain it without notes

01

Why does <canvas> need JavaScript, and what does getContext("2d") give you?

02

What is the difference between immediate mode (canvas) and retained mode (SVG), and when would you pick each?

03

Why can a canvas look blurry, and how do you fix it?

04

How do you make a canvas chart accessible?

Practice

01

Draw a 200 × 200 canvas with a red square of side 100 in the centre, and give it a text alternative.

02

Draw a smiley face (a yellow circle, two black eye circles) on a 150 × 150 canvas.

Trade-offs

  • ↔

    Canvas is faster for many objects but you rebuild accessibility, hit testing and text yourself; SVG gives those for free but slows down with thousands of elements.

Done when you can

  • I can create a canvas with a pixel size and fallback content.

  • I can get the 2D context and draw rectangles, circles and text.

  • I can explain why canvas needs JavaScript and how it differs from SVG.

  • I can avoid blurry canvases by matching bitmap and CSS size.

  • I can give a canvas a proper text alternative.