The Canvas API
Reviewed & published by Brayan K
The Canvas API lets you draw graphics pixel by pixel with JavaScript — shapes, lines, text, and images — by calling methods on a <canvas> element's 2D rendering context.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
It powers charts, games, image editors, data visualizations, and any custom drawing the browser's normal HTML and CSS cannot express.
What You'll Learn in This Lesson
- Get the 2D context with getContext
- Draw rectangles with fillRect/strokeRect
- Build paths with moveTo/lineTo/arc
- Set fillStyle and strokeStyle
- Draw text and clear regions
- Animate with requestAnimationFrame
💡 About the runnable demos: A real <canvas> needs a browser, so the drawing code lives in the read-only blocks. Each Try it Yourself computes the geometry — the exact coordinates a shape would be drawn at — and logs it, which is the part people actually get wrong. Paste the read-only snippets into a browser to see them paint.
🖌️ Real-World Analogy: The canvas is a painter's canvas and the context is your set of brushes and pens. You don't move shapes around like furniture (that's the DOM); you paint them on. Want something to move? You wipe the canvas and repaint the whole scene a little differently — 60 times a second.
1️⃣ The Context, Coordinates & Rectangles
Grab the 2D context with canvas.getContext("2d"). The coordinate system starts at the top-left: X grows right, Y grows down. The quickest things to draw are rectangles: fillRect(x, y, w, h) paints a solid box, strokeRect outlines one, and clearRect erases a region.
To center a box you must offset by half its size — the classic coordinate bug. Let's compute that placement for real:
const canvas = { width: 300, height: 200 };
const box = { w: 80, h: 40 };
// fillRect takes the TOP-LEFT corner, so to center we subtract half
const x = canvas.width / 2 - box.w / 2;
const y = canvas.height / 2 - box.h / 2;
console.log("draw fillRect at:", x, y, box.w, box.h);
// ✅ Expected output: draw fillRect at: 110 80 80 40
// Its right/bottom edges land here:
console.log("right edge:", x + box.w); // ✅ Expected output: right edge: 190
console.log("bottom edge:", y + box.h); // ✅ Expected output: bottom edge: 1202️⃣ Paths, Lines & Arcs
Anything beyond rectangles is a path. Start with beginPath(), move the "pen" with moveTo(x, y), draw straight segments with lineTo(x, y), and draw circles or arcs with arc(cx, cy, radius, startAngle, endAngle) — angles in radians. Finish by calling fill() or stroke().
💡 Tip: Always call beginPath() before a new shape. Forget it and your new path connects to the previous one, producing stray lines.
A circle is really just points placed with trigonometry. Here we compute the points where arc would place markers around a circle — the basis of every pie chart and clock face:
const cx = 100, cy = 100, radius = 50;
// Place 4 points evenly around the circle (every 90°)
const points = [];
for (let i = 0; i < 4; i++) {
const angle = (i / 4) * Math.PI * 2; // 0, 90°, 180°, 270° in radians
const x = Math.round(cx + radius * Math.cos(angle));
const y = Math.round(cy + radius * Math.sin(angle));
points.push([x, y]);
}
console.log("points on the circle:", points);
// ✅ Expected output: points on the circle: [ [ 150, 100 ], [ 100, 150 ], [ 50, 100 ], [ 100, 50 ] ]🎯 Your Turn #1 — Get the Context
Replace the blank with the argument that asks for the 2D drawing context.
// A model canvas that returns a context only for "2d"
const canvas = {
getContext: (type) => (type === "2d" ? { kind: "2d toolbox" } : null),
};
// 👉 replace ___ with the string that requests the 2D context
const ctx = canvas.getContext("___");
console.log(ctx.kind); // ✅ Expected output: 2d toolbox3️⃣ Text, Clearing & the Animation Loop
Draw labels with ctx.font and fillText(text, x, y). To animate, use requestAnimationFrame: it calls your function right before the next repaint. Each frame you clearRect the whole canvas, update positions, redraw, and schedule the next frame. Because the canvas is immediate-mode, nothing persists — you repaint the entire scene every time.
The motion itself is just arithmetic per frame. Here we compute the box's x-position over several frames, including the wrap-around — exactly what the loop above does:
const canvasWidth = 100;
const speed = 30;
// Simulate the x position over 5 animation frames
let x = 0;
const positions = [];
for (let frame = 0; frame < 5; frame++) {
positions.push(x);
x = (x + speed) % canvasWidth; // wrap when it runs off the edge
}
console.log("x per frame:", positions);
// ✅ Expected output: x per frame: [ 0, 30, 60, 90, 20 ]🎯 Your Turn #2 — Build a Grid
Replace the blank so each cell's x is its column times the cell size.
const cell = 25;
const xs = [];
// 3 columns: each rect's left edge = column index * cell size
for (let col = 0; col < 3; col++) {
// 👉 replace ___ so x is the column's pixel position
const x = col * ___;
xs.push(x);
}
console.log(xs); // ✅ Expected output: [ 0, 25, 50 ]🧠 Quick Recall
Predict the answer before revealing it.
The top-left corner. Y increases downward.
Radians. A full circle is Math.PI * 2.
ctx.clearRect(0, 0, canvas.width, canvas.height).
Common Errors & Fixes
- 1️⃣ Forgetting beginPath() between shapes
- 2️⃣ Using degrees instead of radians
- 3️⃣ Sizing the canvas with CSS instead of attributes
- 4️⃣ Animating without clearing
🎯 Mini Challenge — A Tiny Bar Chart's Geometry
Before you can draw a bar chart you must compute each bar's rectangle. Given some values and a chart height, produce the fillRect arguments for each bar so they sit on the baseline and scale to the tallest value.
const values = [3, 6, 9];
const chartHeight = 90;
const barWidth = 20;
const gap = 10;
const max = Math.max(...values);
// For each value, compute [x, y, width, height] for fillRect.
// Bars hang from the baseline, so y = chartHeight - barHeight.
const bars = values.map((v, i) => {
const barHeight = (v / max) * chartHeight; // scale to the tallest
const x = i * (barWidth + gap);
const y = chartHeight - barHeight;
return [x, y, barWidth, barHeight];
});
console.log(bars);
// ✅ Expected output: [ [ 0, 60, 20, 30 ], [ 30, 30, 20, 60 ], [ 60, 0, 20, 90 ] ]Quick Reference — Canvas 2D
| Goal | Code |
|---|---|
| Get context | canvas.getContext("2d") |
| Solid box | ctx.fillRect(x, y, w, h) |
| Outline box | ctx.strokeRect(x, y, w, h) |
| Path | beginPath / moveTo / lineTo |
| Circle | arc(cx, cy, r, 0, Math.PI*2) |
| Text | ctx.fillText(s, x, y) |
| Animate | requestAnimationFrame(frame) |
📚 Keep Learning
- • MDN — Canvas tutorial
- • MDN — CanvasRenderingContext2D
- • MDN — requestAnimationFrame
🎉 Lesson Complete — The Canvas API!
- ✅ Draw on the 2D context from getContext("2d")
- ✅ Coordinates start top-left; arc angles are in radians
- ✅ fill paints inside, stroke outlines; build shapes with paths
- ✅ Animate by clearing and repainting every requestAnimationFrame
Practice quiz
How do you get the 2D drawing context from a canvas element?
- canvas.getContext('2d')
- canvas.context2d()
- canvas.draw('2d')
- canvas.get2D()
Answer: canvas.getContext('2d'). canvas.getContext('2d') returns the CanvasRenderingContext2D object, the toolbox of methods like fillRect, arc, and fillText.
Where is the origin (0, 0) on a canvas?
- The center
- The bottom-left corner
- The top-left corner
- The top-right corner
Answer: The top-left corner. The coordinate system starts at the top-left: X grows right and Y grows downward.
Which method paints a solid filled rectangle?
- strokeRect(x, y, w, h)
- clearRect(x, y, w, h)
- fillRect(x, y, w, h)
- drawRect(x, y, w, h)
Answer: fillRect(x, y, w, h). fillRect(x, y, w, h) paints a solid box; strokeRect outlines one and clearRect erases a region.
What units does arc() use for its start and end angles?
- Degrees
- Radians
- Pixels
- Percentages
Answer: Radians. arc() angles are in radians; a full circle is Math.PI * 2 (using 360 would be wrong).
To draw a custom path, which method should you call first to start a new shape?
- closePath()
- beginPath()
- moveTo()
- fill()
Answer: beginPath(). Always call beginPath() before a new shape; forgetting it makes the new path connect to the previous one, producing stray lines.
Which API is used to animate by running a draw function before each repaint?
- setInterval
- requestAnimationFrame
- setTimeout
- queueMicrotask
Answer: requestAnimationFrame. requestAnimationFrame calls your draw function right before the next repaint (about 60 times a second).
How do you erase the whole canvas at the start of each animation frame?
- ctx.reset()
- ctx.fillRect(0, 0, w, h)
- ctx.clearRect(0, 0, canvas.width, canvas.height)
- ctx.beginPath()
Answer: ctx.clearRect(0, 0, canvas.width, canvas.height). Because canvas is immediate-mode, you clearRect(0, 0, canvas.width, canvas.height) to wipe before repainting each frame.
To center an 80x40 box on a 300x200 canvas, what are the fillRect x and y?
- x: 150, y: 100
- x: 110, y: 80
- x: 80, y: 40
- x: 220, y: 160
Answer: x: 110, y: 80. fillRect takes the top-left corner, so x = 300/2 - 80/2 = 110 and y = 200/2 - 40/2 = 80.
What is the difference between fill and stroke?
- fill outlines, stroke fills inside
- fill paints the inside, stroke draws the outline
- They are identical
- fill is for text, stroke is for shapes
Answer: fill paints the inside, stroke draws the outline. fill paints the inside of a shape using fillStyle; stroke draws only its outline using strokeStyle and lineWidth.
Why should you size a canvas with width/height attributes instead of CSS?
- CSS sizing is slower to load
- Attributes set the real pixel resolution; CSS just stretches the bitmap and looks blurry
- CSS only works in Firefox
- Attributes enable WebGL automatically
Answer: Attributes set the real pixel resolution; CSS just stretches the bitmap and looks blurry. Setting width/height attributes sets the real pixel resolution; using CSS stretches a smaller bitmap and produces a blurry result.
Continue this course
- Previous: IndexedDB (Client-Side Database)
- Next: Checkpoint: Browser APIs — Combine promises, async iterators and a cache in a build — then a quiz
- Quick reference: JavaScript cheat sheet
Frequently asked questions
What is the 2D context and why do I need getContext('2d')?
The <canvas> element is just a blank rectangle of pixels; it has no drawing methods itself. getContext('2d') returns a CanvasRenderingContext2D object — the actual toolbox of methods like fillRect, arc, and fillText. You draw on the context, and it paints onto the canvas. (There is also a 'webgl' context for 3D.)
Why is the canvas coordinate system y-down?
Like most computer graphics, the origin (0, 0) is the TOP-LEFT corner. X increases to the right and Y increases DOWNWARD, so y: 100 is lower on screen than y: 0. This trips up people expecting math-class axes. Angles for arc() are measured in radians, starting at the 3 o'clock position and going clockwise.
What is the difference between fill and stroke?
fill paints the inside of a shape using fillStyle; stroke draws only its outline using strokeStyle and lineWidth. fillRect/strokeRect do this for rectangles directly. For custom paths you build the shape with beginPath/moveTo/lineTo/arc and then call fill() or stroke() (or both) to render it.
How do I animate on a canvas?
Use requestAnimationFrame, which calls your draw function right before the next repaint (about 60 times a second). Each frame you clearRect the whole canvas, update your positions, redraw everything, then call requestAnimationFrame again. Because the canvas is immediate-mode, nothing persists between frames — you repaint the whole scene every time.