2D Visuals with Canvas

Chapter 8: 2D Visuals with Canvas

The HTML5 <canvas> element provides a procedural, pixel-based API for rendering 2D graphics. Unlike SVG (Scalable Vector Graphics), which is part of the DOM and retained in memory, the Canvas is "immediate mode"—once a shape is drawn, the browser "forgets" it, retaining only the resulting pixels. This makes it ideal for high-performance animations, data visualizations, and game engines.


I. The Canvas 2D Context

To draw on a canvas, you must obtain its rendering context. Modern browsers use GPU-accelerated rasterization to convert drawing commands into pixels.

1. Coordinate System

The Canvas coordinate system starts at (0,0) in the top-left corner. X increases to the right, and Y increases downwards.

+X+Y(0,0)


II. Comprehensive API Reference

1. Core Drawing & Paths

MethodSyntaxDescription
fillRect()ctx.fillRect(x, y, w, h)Draws a filled rectangle.
strokeRect()ctx.strokeRect(x, y, w, h)Draws a rectangular outline.
clearRect()ctx.clearRect(x, y, w, h)Sets pixels in the area to transparent black.
beginPath()ctx.beginPath()Starts a new path.
moveTo()ctx.moveTo(x, y)Moves pen to (x,y).
lineTo()ctx.lineTo(x, y)Draws line to (x,y).
arc()ctx.arc(x,y,r,sA,eA,ccw)Circular arc (radians).
bezierCurveTo()ctx.bezierCurveTo(...)Cubic Bézier curve (6 params).
clip()ctx.clip()Turns the current path into a masking region.
isPointInPath()ctx.isPointInPath(x, y)Hit Detection: Returns true if (x,y) is inside path.

2. Text API & Layout

MethodSyntaxDescription
fillText()ctx.fillText(str, x, y, maxW?)Draws filled text.
strokeText()ctx.strokeText(str, x, y, maxW?)Draws text outline.
measureText()ctx.measureText(str)Returns TextMetrics (width, bounding box, etc.).

3. Image API (drawImage Polymorphism)

The drawImage method supports three distinct parameter signatures.

Source (3 params)Scale (5 params)Slice (9 params)

FormParametersUse Case
Simpleimg, dx, dyDraw at original size.
Scaledimg, dx, dy, dw, dhDraw and scale to fit.
Slicingimg, sx, sy, sw, sh, dx, dy, dw, dhSprite Animation: Extract sub-region.

4. Compositing & Blending

The globalCompositeOperation property defines how new shapes are drawn relative to existing pixels.

source-overdestination-indestination-outMASK


III. Implementation Patterns

1. Basic Animation & Interaction

Use requestAnimationFrame for synchronization and save/restore for state management.

// Implementation: Rotating a Square
function drawRotated(ctx, x, y, size, angle) {
  ctx.save();
  ctx.translate(x + size/2, y + size/2);
  ctx.rotate(angle);
  ctx.fillRect(-size/2, -size/2, size, size);
  ctx.restore();
}

// Interactivity: Painting Tool
canvas.addEventListener('mousemove', (e) => {
  if (e.buttons !== 1) return;
  const rect = canvas.getBoundingClientRect();
  ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);
  ctx.stroke();
});

2. Sprite Animation (Slicing)

function drawSpriteFrame(img, frame, width, height, dx, dy) {
  ctx.drawImage(img, frame * width, 0, width, height, dx, dy, width, height);
}

3. Clipping & Lighting (Gradients)

// Circular Masking (Avatars)
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(img, 50, 50, 100, 100);

4. Procedural & Pixel Manipulation

  • Recursive L-Systems: Used for generating trees/fractals.
  • Convolution Kernels: Manipulating ImageData for Sharpen/Blur filters.
// Hit Detection: Pixel-Alpha Check
const pixel = ctx.getImageData(x, y, 1, 1).data;
if (pixel[3] > 0) console.log('Hit!');

5. Multi-threaded Offscreen Rendering

UI ThreadWorker Thread

const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);

IV. Core Engineering Standards

1. Performance Mandates

To achieve a consistent 60FPS (16.6ms frame budget), you must optimize how the browser and GPU interact with the canvas.

A. Canvas Layering (The "Static-Dynamic" Split)

Redrawing a complex background in every frame is an anti-pattern. Instead, use multiple overlapping canvas elements using CSS z-index.

  • Background Layer: Draw static terrain/UI once.
  • Dynamic Layer: Draw only moving entities (cleared every frame).
  • Benefit: Reduces the number of pixels the GPU must rasterize per frame by up to 90%.

B. High-DPI (Retina) Scaling

On modern displays, window.devicePixelRatio is often 2 or 3. If you don't scale your canvas, your graphics will appear blurry because the browser upscales a low-resolution buffer.

function setupHighDPI(canvas) {
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();

  // 1. Scale the internal drawing buffer
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;

  // 2. Use CSS to keep the display size the same
  canvas.style.width = `${rect.width}px`;
  canvas.style.height = `${rect.height}px`;

  // 3. Normalize coordinates so you can draw using CSS pixels
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
  return ctx;
}

C. Path Batching & State Changes

Every call to stroke(), fill(), or changing ctx.fillStyle involves a state switch in the GPU pipeline.

  • Inefficient: Loop { set color, draw shape, stroke }.
  • Efficient: Group shapes by color -> Loop { draw path } -> stroke once.
  • Technical Result: Minimizes "Draw Calls" sent to the GPU, significantly reducing CPU-to-GPU overhead.

D. The Integer Grid

Drawing at non-integer coordinates (e.g., x: 10.5) forces the browser to perform sub-pixel anti-aliasing. This involves sampling adjacent pixels and blending colors, which is computationally expensive and makes graphics look "soft".

  • Production Standard: Always use Math.floor() or bitwise | 0 on coordinates before drawing.
  • Result: Ensures pixel-perfect alignment and bypasses the anti-aliasing engine.

E. Avoiding DOM Access in Loops

Reading properties like canvas.width or window.innerWidth inside an animation loop can trigger Synchronous Layout (Reflow).

  • Architectural Mandate: Cache all required dimensions in local variables outside the requestAnimationFrame loop.

2. Memory Mandates

  • Object Recycling: Use Object Pooling for particle systems to prevent Garbage Collection pauses.
  • Buffer Management: Reuse Uint8ClampedArray buffers for high-frequency ImageData processing.

Frame State: [OK ]