🎨 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
requestAnimationFrameand 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.
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.
| Aspect | Canvas | SVG |
|---|---|---|
| Model | Immediate mode (pixels) | Retained mode (DOM objects) |
| Individual elements | None — one bitmap | Each shape is a node |
| Events | Manual hit-testing | Native per-shape events |
| Scaling | Resolution-dependent | Resolution-independent |
| Many objects | Excellent | Slows down |
| Accessibility | Manual (fallback DOM) | Good, semantic |
| Best for | Games, pixels, dense updates | UI, icons, interactive diagrams |
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
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:
- Add a
<canvas width="400" height="300">and get its 2D context. - Track a ball with
x,y,dx,dy, and a radius. - In each frame: clear, move the ball, and reverse
dxordywhen it hits an edge. - Draw the ball with
arc()+fill()and loop withrequestAnimationFrame.
💡 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
requestAnimationFrameand delta time - Scale the backing store by
devicePixelRatiofor 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
clearRecteach 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
- MDN — Canvas API
- MDN — Canvas tutorial
- Konva.js and PixiJS — libraries for larger projects
🚀 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.