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

💡 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: 120

2️⃣ 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 toolbox

3️⃣ 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

🎯 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

GoalCode
Get contextcanvas.getContext("2d")
Solid boxctx.fillRect(x, y, w, h)
Outline boxctx.strokeRect(x, y, w, h)
PathbeginPath / moveTo / lineTo
Circlearc(cx, cy, r, 0, Math.PI*2)
Textctx.fillText(s, x, y)
AnimaterequestAnimationFrame(frame)

📚 Keep Learning

🎉 Lesson Complete — The Canvas API!

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

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.