Skip to main content

🎨 Canvas API Introduction

Where SVG builds a scene out of DOM objects, Canvas hands you a blank bitmap and a paintbrush made of JavaScript. It's fast, immediate, and perfect for games, particle effects, and pixel work. In this lesson you'll set up a canvas, draw shapes and text, apply colors and gradients, transform the coordinate space, and run a smooth animation loop.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain immediate-mode rendering and contrast Canvas with SVG's retained mode
  • Set up a <canvas> and obtain its 2D drawing context
  • Draw rectangles, paths, arcs, and text, and apply fills, strokes, and gradients
  • Use save()/restore() and transformations to position drawings
  • Build an animation loop with requestAnimationFrame and time-based motion

Estimated Time: 35–45 minutes  •  Difficulty: Intermediate

Hands-on: Animate a ball that bounces off the edges of the canvas.

In This Lesson

What Is the Canvas API?

The HTML5 Canvas API gives you a rectangular drawing surface and a JavaScript object — the context — with methods for painting shapes, text, and images onto it. Everything is drawn in immediate mode: once you paint a pixel, it becomes part of the flat bitmap and forgets it was ever a "circle." There is no list of objects to update later.

💡 A useful analogy: Canvas is like painting on an actual canvas. Once the brush touches the surface, the paint is the surface — you can't grab "that circle" and move it. To change the scene, you wipe the area and repaint. SVG, by contrast, is like arranging cut-out shapes on a felt board: each piece stays an object you can pick up.
flowchart TD A["<canvas> element"] -->|getContext '2d'| B[2D context object] B -->|paints| C[Flat bitmap of pixels] C -->|to change| D[Clear region & redraw] D --> C

That "fire-and-forget" model is exactly what makes Canvas fast. It shines for:

  • Games & animations — thousands of moving sprites or particles
  • Data visualization at scale — dense scatter plots, heatmaps
  • Image processing — filters and per-pixel manipulation
  • Drawing & painting tools, and generative art

Canvas vs. SVG

Both draw 2D graphics in the browser, but they're built on opposite philosophies. Neither is "better" — the right choice depends on whether you need objects you can inspect or raw drawing throughput.

AspectCanvasSVG
ModelImmediate mode (pixels)Retained mode (DOM objects)
Individual elementsNone — one bitmapEach shape is a node
EventsManual hit-testingNative per-shape events
ScalingResolution-dependentResolution-independent
Many objectsExcellentSlows down
AccessibilityManual (fallback DOM)Good, semantic
Best forGames, pixels, dense updatesUI, icons, interactive diagrams
Canvas pixels versus SVG objects Canvas stores a flat grid of pixels with no separate shapes; SVG keeps each shape as an object with editable nodes. Canvas — pixels no objects, just a grid SVG — objects editable nodes remain
Figure 1 — Canvas flattens everything to pixels; SVG keeps each shape as a live object. That single difference drives every trade-off in the table above.

Setting Up a Canvas

Every Canvas program starts the same way: an element in the HTML, then JavaScript to grab its 2D context.

<canvas id="scene" width="600" height="400">
  Your browser does not support the canvas element.
</canvas>
const canvas = document.getElementById('scene');
const ctx = canvas.getContext('2d');   // the drawing context

ctx.fillStyle = 'royalblue';
ctx.fillRect(50, 50, 100, 75);         // x, y, width, height

⚠️ Width/height attributes vs. CSS

The width/height attributes set the bitmap's real pixel resolution. CSS width/height only stretch that bitmap, which causes blur. Always set the drawing resolution via attributes (or in JS), and use CSS only for layout sizing. On high-DPI screens, multiply the resolution by window.devicePixelRatio for crisp output.

  • Text between the tags is fallback content for unsupported browsers.
  • getContext('2d') returns the object carrying every drawing method.
  • The origin (0, 0) is the top-left; X grows right, Y grows down — same as SVG.

Drawing Shapes & Paths

Rectangles have their own one-call methods. Everything else is drawn as a path: begin a path, describe it, then fill() or stroke() it.

Rectangles

ctx.fillRect(x, y, w, h);      // filled
ctx.strokeRect(x, y, w, h);    // outlined
ctx.clearRect(x, y, w, h);     // erase back to transparent

Lines and custom paths

ctx.strokeStyle = 'green';
ctx.lineWidth = 4;
ctx.lineCap = 'round';

ctx.beginPath();               // start fresh
ctx.moveTo(50, 50);            // pen up, jump to a point
ctx.lineTo(200, 50);          // draw to a point
ctx.lineTo(200, 100);
ctx.closePath();               // optional: line back to start
ctx.stroke();                  // actually render the outline

Arcs and circles

// arc(centerX, centerY, radius, startAngle, endAngle) — angles in radians
ctx.beginPath();
ctx.arc(150, 100, 40, 0, Math.PI * 2);   // full circle
ctx.fillStyle = 'tomato';
ctx.fill();

💡 The golden rule of Canvas paths

Always call beginPath() before describing a new shape. Forget it, and the new path appends to the previous one — so a later stroke() re-draws everything and colors bleed across shapes. beginPath() → describe → fill()/stroke() is the reliable rhythm.

Colors, Gradients & Text

Set fillStyle and strokeStyle before you draw — they apply to every subsequent operation until you change them.

ctx.fillStyle = '#ff0000';                 // hex
ctx.fillStyle = 'rgb(255 0 0)';            // rgb
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';   // 50% transparent

// Linear gradient
const grad = ctx.createLinearGradient(0, 0, 200, 0);
grad.addColorStop(0, '#3b82f6');
grad.addColorStop(1, '#22c55e');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 100);

Text

ctx.font = 'bold 24px system-ui, sans-serif';
ctx.fillStyle = '#0f172a';
ctx.textAlign = 'center';          // start | end | left | right | center
ctx.textBaseline = 'middle';       // top | middle | alphabetic | bottom

ctx.fillText('Hello Canvas!', 150, 60);
ctx.strokeText('Outlined', 150, 100);

const { width } = ctx.measureText('Hello Canvas!');   // measure to lay out

⚠️ Canvas text is just pixels

Text drawn to a canvas can't be selected, searched, or read by a screen reader — it's paint. For any text that carries meaning, provide it again in accessible HTML (fallback content or an adjacent element). This is a key reason to reach for SVG or plain DOM when text matters.

State & Transformations

The context carries a state: current styles plus a transformation matrix. translate, rotate, and scale move the coordinate system rather than the shapes, so you draw at a simple origin and let the transform place it.

The essential pair is save() and restore() — push the current state onto a stack, make temporary changes, then pop back. Without them, a rotation would linger and corrupt everything you draw afterward.

ctx.save();                    // remember current state
ctx.translate(150, 100);       // move origin to a pivot point
ctx.rotate(Math.PI / 4);       // 45°, in radians
ctx.fillStyle = 'purple';
ctx.fillRect(-25, -25, 50, 50);// centered on the new origin
ctx.restore();                 // undo translate + rotate + fillStyle

ctx.fillRect(0, 0, 40, 40);    // back to normal, un-rotated
Canvas transformations A square shown in original, translated, rotated, and scaled states. original translate rotate(45°) scale(1.4)
Figure 2 — Transformations move the coordinate system. Wrap them in save()/restore() so each only affects the shapes you intend.

The Animation Loop

Canvas animation is a flip-book: each frame you clear the surface, update positions, and redraw. Schedule frames with requestAnimationFrame — it syncs to the display's refresh rate and automatically pauses on inactive tabs.

let x = 0;
const speed = 100;      // pixels per second
let last = 0;

function frame(now) {
  const dt = (now - last) / 1000;   // seconds since last frame
  last = now;

  // 1. clear
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 2. update (time-based = same speed on any device)
  x += speed * dt;
  if (x > canvas.width) x = 0;

  // 3. draw
  ctx.fillStyle = 'royalblue';
  ctx.fillRect(x, 50, 50, 50);

  requestAnimationFrame(frame);     // schedule the next frame
}

requestAnimationFrame(frame);       // start

✅ Multiply by delta time

Moving a fixed number of pixels per frame makes your animation run faster on a 144 Hz monitor than a 60 Hz one. Multiplying speed by dt (elapsed seconds) makes motion frame-rate independent, so it looks identical everywhere.

Hands-on Exercise

🏋️ Bounce a ball off the walls

Objective: Combine setup, drawing, and the animation loop to make a ball travel and bounce.

Instructions:

  1. Add a <canvas width="400" height="300"> and get its 2D context.
  2. Track a ball with x, y, dx, dy, and a radius.
  3. In each frame: clear, move the ball, and reverse dx or dy when it hits an edge.
  4. Draw the ball with arc() + fill() and loop with requestAnimationFrame.
💡 Hint

The ball hits the right wall when x + radius >= canvas.width, and the left wall when x - radius <= 0. On a hit, flip the velocity: dx = -dx. Same idea vertically with dy.

✅ Sample solution
const canvas = document.getElementById('scene');
const ctx = canvas.getContext('2d');

const ball = { x: 100, y: 80, dx: 180, dy: 120, r: 16 };
let last = 0;

function frame(now) {
  const dt = (now - last) / 1000;
  last = now;

  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ball.x += ball.dx * dt;
  ball.y += ball.dy * dt;

  if (ball.x + ball.r >= canvas.width || ball.x - ball.r <= 0) ball.dx = -ball.dx;
  if (ball.y + ball.r >= canvas.height || ball.y - ball.r <= 0) ball.dy = -ball.dy;

  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
  ctx.fillStyle = '#3b82f6';
  ctx.fill();

  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

🎯 Quick Quiz

Question 1: Why can't you move an individual circle after drawing it on a canvas?

Question 2: What is the recommended way to schedule frames in a Canvas animation?

Question 3: You need an interactive diagram where each node has its own click handler and readable label. Which technology fits best?

Best Practices

✅ Do

  • Call beginPath() before each new shape
  • Drive motion with requestAnimationFrame and delta time
  • Scale the backing store by devicePixelRatio for crisp HiDPI output
  • Wrap temporary transforms in save()/restore()
  • Provide accessible fallback content inside the <canvas> tag

⚠️ Don't

  • Size a canvas with CSS alone — it stretches and blurs the bitmap
  • Rely on canvas text for meaningful, readable content
  • Forget to clearRect each frame (you'll get smears — unless a trail is intended)
  • Reach for Canvas when SVG's per-element events would be simpler

Summary & Quiz

🎉 Key Takeaways

  • Canvas is an immediate-mode bitmap: draw, and the pixels are set.
  • All drawing goes through the 2D context from getContext('2d').
  • Shapes beyond rectangles are paths: beginPath → describe → fill/stroke.
  • save()/restore() keep transformations from leaking between draws.
  • Animate by clearing and redrawing inside requestAnimationFrame, scaled by delta time.

📚 Further Reading

🚀 What's Next?

You've now seen both drawing models. Next we go deeper on the SVG side — Inline SVG and Manipulation — where the DOM friendliness we contrasted with Canvas becomes a superpower for building interactive, data-driven graphics with JavaScript.

🎉 Nice work!

You can paint, transform, and animate on the canvas. Let's return to SVG and make it move.