Lesson 38 β’ Advanced Track
HTML Canvas Essentials
By the end you'll draw shapes, text, and gradients on a <canvas>, capture mouse input to draw freehand, and run a smooth animation loop with requestAnimationFrame.
What You'll Learn
- Get a 2D drawing context with getContext('2d') and draw on the bitmap
- Draw rectangles, circles, triangles, and custom paths with arc, moveTo, and lineTo
- Tell fill from stroke and apply colours, line widths, and linear gradients
- Render text, draw images, and clear the canvas with clearRect
- Capture mouse events to build an interactive freehand drawing pad
- Animate at ~60fps with requestAnimationFrame and bounce objects off walls
Before you start: Canvas is driven entirely by JavaScript, so you'll want to be comfortable with variables, functions, and events first. If you need a refresher, revisit the previous lesson and the JavaScript course.
π‘ Real-World Analogy
Think of canvas as a whiteboard with a single marker. You don't place objects you can move later β you give the browser instructions ("put the pen here, draw a circle, fill it green"), and it stains the board with pixels. Once a stroke is down it isn't a separate "thing" anymore; to change it you wipe the area and redraw. That's why animation means clear, then redraw the whole scene on every frame.
1. The drawing context
The <canvas> element is just a blank rectangle until you grab its 2D context β the object that holds every drawing method. You get it once and reuse it:
const canvas = document.getElementById('art');
const ctx = canvas.getContext('2d'); // ctx = your "pen"
ctx.fillStyle = 'tomato'; // set a colourβ¦
ctx.fillRect(10, 10, 100, 60); // β¦and paint a 100Γ60 box at (10,10)Coordinates start at (0, 0) in the top-left; x grows to the right and y grows downward. Canvas is great for games, charts, and effects; for icons and logos prefer SVG (next lesson) because it stays sharp at any zoom.
Canvas vs SVG β pick the right tool
| Feature | Canvas | SVG |
|---|---|---|
| Type | Pixel-based (bitmap) | Vector-based (DOM nodes) |
| Scaling | Gets blurry on zoom | Infinitely sharp |
| Best for | Games, visualisations, effects | Icons, logos, diagrams |
| Interactivity | Manual hit-testing | Native DOM events per element |
| Performance | Better with many objects | Slows with 100s of nodes |
2. Shapes, fills, strokes, text & gradients
Read this fully-commented example, then run it. Notice three patterns you'll use constantly: fill vs stroke (inside colour vs outline), paths (beginPath β arc/lineTo β fill/stroke), and a gradient built from colour stops.
Worked example: shapes, fills & gradients
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
/* Set the drawing size with width/height ATTRIBUTES below, not CSS */
canvas { border: 2px solid #ddd; border-radius: 8px; background: #fafafa; display: block; }
</style>
</head>
<body>
<h1>Shapes, Fills & Strokes</h1>
<!-- width/height here set the real pixel resolution of the bitmap -->
<canvas id="art" width="500" height="300">
Your browser does not support canvas. <!-- fallback
... Important: Set canvas size with HTML attributes (<canvas width="500" height="300">), not CSS. CSS only stretches the bitmap β the real resolution stays at the attribute values, so mismatched CSS sizes produce blurry output.
3. π― Your turn β a traffic light
Time to practise fill and beginPath. Fill in the blanks marked ___ following the π hints. Each lamp is its own path, so beginPath() before every circle.
π― YOUR TURN: traffic light
<!DOCTYPE html>
<html>
<head><style>
body { font-family: system-ui, sans-serif; padding: 24px; }
canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; display: block; }
</style></head>
<body>
<h1>π― YOUR TURN β Draw a Traffic Light</h1>
<canvas id="light" width="160" height="320">Canvas not supported.</canvas>
<script>
const ctx = document.getElementById('light').getContext('2d');
// The black backing box is done for you:
ctx.fillStyle = '#222';
ctx.fi
...4. Capturing mouse input
To draw freehand you turn mouse positions into a path. On mousedown you start a path, on mousemove you lineTo the new point and stroke(), and you stop on mouseup. getBoundingClientRect() converts the page coordinates into canvas-local ones.
Worked example: interactive drawing pad
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; cursor: crosshair; display: block; margin-top: 12px; }
.controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.controls button { padding: 8px 16px; border: 2px solid #1976D2; background: transparent; color: #1976D2; border-radius: 6px; cursor: pointer; font-weight: 600; }
.controls button:hover { b
...5. Animating with requestAnimationFrame
An animation is just a function that draws one frame and then asks to be called again. requestAnimationFrame(fn) runs fn right before the next repaint (~60 times/sec). Each frame you clear (or paint a faint layer for trails), update positions, redraw, then schedule the next frame.
Worked example: bouncing balls
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
canvas { border: 2px solid #ddd; border-radius: 8px; background: #1a1a2e; display: block; }
</style>
</head>
<body>
<h1>Bouncing Balls β requestAnimationFrame</h1>
<canvas id="anim" width="500" height="300">Canvas not supported.</canvas>
<script>
const ctx = document.getElementById('anim').getContext('2d');
// Build a few balls, each with a position, velocity (dx/dy), radius, col
...6. π― Your turn β a moving square
Build your own animation loop. Fill in the three blanks: the clear method, and the function name passed to requestAnimationFrame so the loop keeps going.
π― YOUR TURN: sliding square
<!DOCTYPE html>
<html>
<head><style>
body { font-family: system-ui, sans-serif; padding: 24px; }
canvas { border: 2px solid #ddd; border-radius: 8px; background: #1a1a2e; display: block; }
</style></head>
<body>
<h1>π― YOUR TURN β Slide a Square Across</h1>
<canvas id="stage" width="500" height="160">Canvas not supported.</canvas>
<script>
const ctx = document.getElementById('stage').getContext('2d');
let x = 0; // the square's left edge
function frame() {
...7. A real example β bar chart from data
Loops + rectangles + text alignment combine into a genuine data visualisation. Watch how textAlign = 'center' changes what the x coordinate means for the labels.
Worked example: bar chart
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; display: block; }
</style>
</head>
<body>
<h1>Bar Chart from Data</h1>
<canvas id="chart" width="500" height="300">Canvas not supported.</canvas>
<script>
const ctx = document.getElementById('chart').getContext('2d');
const data = [
{ label: 'Jan', value: 65 }, { label: 'Feb', value: 45 },
{ label:
...8. π― Mini-challenge β smiley face
No blanks this time β just a comment outline. Combine everything you've learned (paths, arcs, fills, strokes) to draw a smiley face. The trick for the smile: an arc from 0 to Math.PI draws the bottom half of a circle.
π― MINI-CHALLENGE: smiley face
<!DOCTYPE html>
<html>
<head><style>
body { font-family: system-ui, sans-serif; padding: 24px; }
canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; display: block; }
</style></head>
<body>
<h1>π― MINI-CHALLENGE β Smiley Face</h1>
<canvas id="face" width="240" height="240">Canvas not supported.</canvas>
<script>
const ctx = document.getElementById('face').getContext('2d');
// π― MINI-CHALLENGE: draw a smiley face from scratch.
// 1. Head: a yellow filled
...When to reach for canvas
- Games: sprite rendering, collision detection, particle effects
- Data visualisation: custom charts, graphs, and infographics
- Image manipulation: filters, cropping, drawing on photos with
drawImage - Signatures: capturing handwritten input on forms
- NOT for: simple icons, logos, or text β use SVG or HTML instead
Common Errors & Fixes
- TypeError: Cannot read properties of null (reading 'getContext') β your
getElementByIdreturnednull. The<script>ran before the canvas existed, or the id is misspelled. Put the script after the canvas (or in a load handler) and check the id. - Everything joins into one strange blob β you forgot
beginPath(). The path accumulates until you start a new one; callctx.beginPath()before each shape. - Shape appears but has no colour β you set
fillStylebut never calledfill()(or setstrokeStylebut neverstroke()). Defining a path doesn't paint it. - Drawing looks blurry / stretched β you sized the canvas with CSS instead of the
width/heightattributes. Set the resolution with attributes. - Animation flickers or smears β you didn't clear the previous frame. Call
ctx.clearRect(0, 0, w, h)at the top of each frame (or a semi-transparentfillRectfor trails).
π Quick Reference
| Method / Property | What it does |
|---|---|
canvas.getContext('2d') | Get the 2D drawing context (the "pen") |
fillStyle / strokeStyle | Colour for fills / for outlines |
fillRect(x,y,w,h) | Draw a filled rectangle |
strokeRect(x,y,w,h) | Draw a rectangle outline |
clearRect(x,y,w,h) | Erase a rectangular area to transparent |
beginPath() | Start a new path (call before each shape) |
arc(x,y,r,0,Math.PI*2) | Add a circle (or arc) to the path |
moveTo(x,y) / lineTo(x,y) | Move pen without drawing / draw a line |
fill() / stroke() | Paint the current path's inside / outline |
fillText(text,x,y) | Draw text (y is the baseline) |
createLinearGradient(...) | Make a gradient for fillStyle/strokeStyle |
drawImage(img,x,y) | Draw a loaded image onto the canvas |
requestAnimationFrame(fn) | Run fn before the next repaint (~60fps) |
Frequently Asked Questions
What is the HTML <canvas> element used for?
Canvas is a pixel-based drawing surface you control with JavaScript. It is ideal for games, data visualisations, image editing, charts, signature pads, and real-time effects. For static icons, logos, and diagrams, SVG is usually a better fit because it scales without blurring and its parts are real DOM elements.
Why is my canvas drawing blurry or stretched?
You almost certainly set the size with CSS instead of the HTML width and height attributes. CSS only stretches the existing bitmap, so the resolution stays low and the pixels smear. Always size the canvas with attributes like <canvas width="500" height="300">, and only use CSS if you genuinely want to stretch the result.
What is the difference between fill and stroke?
Fill paints the inside of a shape using fillStyle, while stroke paints only its outline using strokeStyle and lineWidth. fillRect and strokeRect are shortcuts for rectangles; for paths you call ctx.fill() and/or ctx.stroke() after defining the path. You can do both on the same shape to get an outlined, filled shape.
Why do all my shapes connect into one weird blob?
You forgot beginPath(). The canvas keeps adding to the current path until you start a new one, so every arc and lineTo joins the previous shape. Call ctx.beginPath() before each new shape and the problem disappears.
How do animations work on canvas?
Use requestAnimationFrame(callback). The browser calls your function about 60 times per second, right before each repaint. Inside it you clear the canvas (or paint a semi-transparent layer for trails), redraw everything at its new position, then call requestAnimationFrame again to schedule the next frame.
Is canvas accessible to screen readers?
Not by default β canvas pixels are invisible to assistive tech. Put meaningful fallback text between the <canvas> tags, add an aria-label or role, and where possible provide the same information in plain HTML nearby. For purely decorative drawings, mark them so screen readers can skip them.
π Lesson Complete
- β
getContext('2d')hands you the canvas drawing API - β
fillRect,arc, andmoveTo/lineTodraw the basic shapes - β Fill paints the inside; stroke paints the outline
- β
createLinearGradientblends colours;fillTextrenders text - β
Mouse events +
lineTo/strokeenable freehand drawing - β
clearRect+requestAnimationFrameproduce smooth animation - β Canvas is pixel-based; SVG is vector-based β choose by use case
- β Size with HTML attributes (not CSS) and add fallback text for accessibility
Up next: SVG Mastery β the vector counterpart to canvas, perfect for crisp icons and diagrams.
Sign up for free to track which lessons you've completed and get learning reminders.