|$ curl https://forge-ai.dev/api/markdown?path=docs/html/canvas
$cat docs/html-canvas.md
updated Recently·35 min read·published

HTML Canvas

HTMLCanvasGraphicsAdvanced
Introduction

The <canvas> element provides a bitmap drawing surface that is rendered via JavaScript. Unlike SVG which retains a DOM representation of every shape, canvas operates as an immediate-mode API — once something is drawn, it becomes pixels and is forgotten by the system.

Canvas supports two primary rendering contexts: 2d for raster 2D graphics, and webgl/webgl2 for hardware-accelerated 3D. This guide focuses on the 2D context, which is ideal for data visualizations, image processing, game rendering, and real-time animation.

Getting Started

The canvas element is declared in HTML and accessed via JavaScript. The getContext("2d") method returns a drawing context with methods for rendering shapes, text, images, and more.

canvas-setup.html
HTML
1<canvas id="myCanvas" width="800" height="600">
2 <p>Your browser does not support the canvas element.</p>
3</canvas>

The width and height attributes set the drawing surface size in logical pixels. CSS sizing only affects display size, not resolution. Always set these attributes explicitly — omitting them defaults to 300x150.

context-setup.js
JavaScript
1const canvas = document.getElementById("myCanvas");
2const ctx = canvas.getContext("2d");
3
4// Coordinate system: origin (0,0) at top-left
5// x increases rightward, y increases downward
6ctx.fillStyle = "#00FF41";
7ctx.fillRect(10, 10, 100, 50); // x, y, width, height

info

Fallback content inside the <canvas> tags is displayed only when canvas is unsupported. Always include a meaningful fallback message, or better yet, a static image alternative.

The coordinate system places (0,0) at the top-left corner. The x-axis increases to the right, and the y-axis increases downward. All drawing operations are expressed in this coordinate space.

preview
Drawing Shapes

Canvas provides two approaches for drawing shapes: convenience methods for simple rectangles, and path-based drawing for arbitrary shapes.

Rectangles

Rectangles are the only shapes with dedicated methods. All other shapes must be drawn using paths.

rectangles.js
JavaScript
1// Fill a rectangle
2ctx.fillStyle = "#00FF41";
3ctx.fillRect(50, 50, 200, 100); // x, y, w, h — solid fill
4
5// Stroke (outline) a rectangle
6ctx.strokeStyle = "#FF6B6B";
7ctx.lineWidth = 3;
8ctx.strokeRect(50, 200, 200, 100); // x, y, w, h — outline only
9
10// Clear a rectangular area (transparent)
11ctx.clearRect(75, 75, 50, 50); // cuts a hole
Paths

Paths define arbitrary shapes as sequences of connected points. Every path operation follows this pattern: beginPath() to start, drawing commands to define the shape, then fill() or stroke() to render.

paths.js
JavaScript
1ctx.beginPath();
2ctx.moveTo(100, 50); // starting point
3ctx.lineTo(200, 150); // line to (200,150)
4ctx.lineTo(50, 150); // line to (50,150)
5ctx.closePath(); // closes back to (100,50)
6ctx.fillStyle = "#00FF41";
7ctx.fill(); // fills the triangle
8ctx.strokeStyle = "#FFFFFF";
9ctx.lineWidth = 2;
10ctx.stroke(); // strokes the outline
11
12// Arcs and circles
13ctx.beginPath();
14ctx.arc(150, 150, 50, 0, Math.PI * 2); // full circle
15ctx.fillStyle = "#4FC3F7";
16ctx.fill();
17
18// Arc segment (pie slice)
19ctx.beginPath();
20ctx.arc(300, 150, 60, 0, Math.PI * 1.5); // 270 degrees
21ctx.lineTo(300, 150);
22ctx.closePath();
23ctx.fillStyle = "#FFD93D";
24ctx.fill();

The arc() method takes: x, y, radius, startAngle, endAngle, and an optional counterclockwise boolean. Angles are measured in radians.

MethodParametersDescription
beginPath()Resets the current path
moveTo(x, y)x, yMoves pen without drawing
lineTo(x, y)x, yDraws a straight line
arc(x, y, r, s, e)x, y, radius, start, endDraws an arc or circle
rect(x, y, w, h)x, y, width, heightAdds a rectangle sub-path
closePath()Closes path back to start
fill()Fills the current path
stroke()Strokes the current path
preview
Colors & Styles

Canvas supports solid colors, gradients, and patterns for both fill and stroke operations. The globalAlpha property controls the overall transparency of all drawing operations.

Solid Colors
solid-colors.js
JavaScript
1// Named, hex, rgb, hsl, or rgba strings
2ctx.fillStyle = "#00FF41";
3ctx.fillStyle = "rgb(0, 255, 65)";
4ctx.fillStyle = "rgba(0, 255, 65, 0.5)"; // with alpha
5ctx.fillStyle = "hsl(135, 100%, 50%)";
6
7// globalAlpha applies to everything drawn after it
8ctx.globalAlpha = 0.5;
9ctx.fillStyle = "#FF6B6B";
10ctx.fillRect(50, 50, 100, 100); // drawn at 50% opacity
11ctx.globalAlpha = 1.0; // reset
Gradients

Linear gradients transition colors along a directional line. Radial gradients transition outward from a center point.

gradients.js
JavaScript
1// Linear gradient
2const linearGrad = ctx.createLinearGradient(0, 0, 200, 0);
3linearGrad.addColorStop(0, "#00FF41");
4linearGrad.addColorStop(0.5, "#4FC3F7");
5linearGrad.addColorStop(1, "#C084FC");
6ctx.fillStyle = linearGrad;
7ctx.fillRect(10, 10, 200, 100);
8
9// Radial gradient
10const radialGrad = ctx.createRadialGradient(150, 150, 10, 150, 150, 100);
11radialGrad.addColorStop(0, "#FFD93D");
12radialGrad.addColorStop(1, "#FF6B6B");
13ctx.fillStyle = radialGrad;
14ctx.beginPath();
15ctx.arc(150, 150, 100, 0, Math.PI * 2);
16ctx.fill();
Patterns

Patterns fill shapes with a repeating image, canvas, or video source.

patterns.js
JavaScript
1// Pattern from an image
2const img = new Image();
3img.src = "tile.png";
4img.onload = () => {
5 const pattern = ctx.createPattern(img, "repeat"); // repeat, repeat-x, repeat-y, no-repeat
6 ctx.fillStyle = pattern;
7 ctx.fillRect(0, 0, canvas.width, canvas.height);
8};
9
10// Pattern from another canvas
11const patternCanvas = document.createElement("canvas");
12patternCanvas.width = 20;
13patternCanvas.height = 20;
14const pCtx = patternCanvas.getContext("2d");
15pCtx.fillStyle = "#00FF41";
16pCtx.fillRect(0, 0, 10, 10);
17const pattern = ctx.createPattern(patternCanvas, "repeat");
18ctx.fillStyle = pattern;
19ctx.fillRect(0, 0, 400, 200);
preview
Text

Canvas provides text rendering similar to CSS, but without automatic line wrapping or layout. You must measure and position text manually.

text.js
JavaScript
1// Configure font before drawing
2ctx.font = "bold 24px monospace";
3ctx.textAlign = "left"; // left, right, center, start, end
4ctx.textBaseline = "alphabetic"; // top, hanging, middle, alphabetic, ideographic, bottom
5ctx.direction = "ltr"; // ltr, rtl, inherit
6
7// Filled text
8ctx.fillStyle = "#00FF41";
9ctx.fillText("Hello, Canvas!", 50, 100); // text, x, y
10
11// Stroked text (outline only)
12ctx.strokeStyle = "#FF6B6B";
13ctx.lineWidth = 1;
14ctx.strokeText("Hello, Canvas!", 50, 150);
15
16// Both fill and stroke
17ctx.font = "bold 48px monospace";
18ctx.fillStyle = "#00FF41";
19ctx.fillText("STACK", 50, 220);
20ctx.strokeStyle = "#FFFFFF";
21ctx.lineWidth = 1;
22ctx.strokeText("STACK", 50, 220);
23
24// Measure text width
25const metrics = ctx.measureText("Hello, Canvas!");
26console.log(metrics.width); // pixel width
27console.log(metrics.actualBoundingBoxAscent);
28console.log(metrics.actualBoundingBoxDescent);

info

Text alignment is relative to the (x, y) position. With textAlign = "center", the x coordinate becomes the center of the text. With textBaseline = "middle", y becomes the vertical center.
preview
Images

The drawImage() method has three variants for rendering images, other canvases, and video frames onto the canvas. Pixel-level manipulation enables image filtering and processing.

drawImage Variants
draw-image.js
JavaScript
1const img = new Image();
2img.src = "photo.jpg";
3img.onload = () => {
4 // Variant 1: Basic — draw at original size
5 ctx.drawImage(img, 10, 10);
6
7 // Variant 2: Scaled — specify destination size
8 ctx.drawImage(img, 10, 10, 200, 150);
9
10 // Variant 3: Source clipping + destination sizing
11 // (sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH)
12 ctx.drawImage(img, 50, 50, 100, 100, 10, 10, 200, 200);
13};
14
15// Draw from another canvas
16ctx.drawImage(offscreenCanvas, 0, 0);
17
18// Draw current video frame
19ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
20
21// Control image smoothing
22ctx.imageSmoothingEnabled = false; // nearest-neighbor (pixel art)
23ctx.drawImage(img, 0, 0, 400, 400);
24ctx.imageSmoothingEnabled = true; // bilinear (default)
Pixel Manipulation

Canvas provides direct access to pixel data for image processing. Each pixel is represented by four values in the Uint8ClampedArray: red, green, blue, and alpha (0–255).

pixel-manipulation.js
JavaScript
1// Read pixel data
2const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
3const data = imageData.data; // Uint8ClampedArray
4
5// Manual grayscale filter
6for (let i = 0; i < data.length; i += 4) {
7 const gray = data[i] * 0.299 + data[i+1] * 0.587 + data[i+2] * 0.114;
8 data[i] = gray; // red
9 data[i+1] = gray; // green
10 data[i+2] = gray; // blue
11 // data[i+3] — alpha (unchanged)
12}
13
14// Write pixels back
15ctx.putImageData(imageData, 0, 0);
16
17// Create blank ImageData
18const newData = ctx.createImageData(100, 100);
19
20// Invert colors
21for (let i = 0; i < data.length; i += 4) {
22 data[i] = 255 - data[i];
23 data[i+1] = 255 - data[i+1];
24 data[i+2] = 255 - data[i+2];
25}
26ctx.putImageData(imageData, 0, 0);

warning

getImageData() can be expensive for large canvases. Avoid calling it every frame in an animation loop. Also note that it is subject to CORS restrictions — images from external origins must be served with the appropriate crossOrigin attribute.
Transformations

Transformations modify the canvas coordinate system before drawing. They accumulate and affect all subsequent operations. Use save() and restore() to isolate transforms.

transformations.js
JavaScript
1// Save current state (transform + styles)
2ctx.save();
3
4// Translate — move origin to new position
5ctx.translate(100, 50);
6
7// Rotate — around current origin (in radians)
8ctx.rotate(Math.PI / 4); // 45 degrees
9
10// Scale — stretch coordinates
11ctx.scale(2, 1.5); // x2 horizontal, x1.5 vertical
12
13// Draw rotated/scaled shape
14ctx.fillStyle = "#00FF41";
15ctx.fillRect(-25, -25, 50, 50);
16
17// Restore to pre-transform state
18ctx.restore();
19
20// Transform matrix — full control
21// (a, b, c, d, e, f)
22// [a c e] [x]
23// [b d f] * [y]
24// [0 0 1] [1]
25ctx.setTransform(1, 0.5, 0, 1, 50, 50); // skew
26ctx.fillRect(0, 0, 100, 100);
27
28// Reset to identity matrix
29ctx.setTransform(1, 0, 0, 1, 0, 0);

Transformations apply in reverse order: translate → rotate → scale means scale happens first in the local coordinate system, then rotate, then translate to the final position. This is why you typically translate first, then rotate, then draw at (0,0).

preview
Animation

Canvas animation uses requestAnimationFrame to create smooth, efficient animations. The browser calls your callback before each repaint (typically 60fps), and provides a high-resolution timestamp for calculating delta time.

game-loop.js
JavaScript
1const canvas = document.getElementById("game");
2const ctx = canvas.getContext("2d");
3
4let lastTime = 0;
5let x = 100;
6let vx = 200; // pixels per second
7
8function gameLoop(timestamp) {
9 // Calculate delta time in seconds
10 const dt = (timestamp - lastTime) / 1000;
11 lastTime = timestamp;
12
13 // Clear the canvas
14 ctx.clearRect(0, 0, canvas.width, canvas.height);
15
16 // Update
17 x += vx * dt; // frame-rate independent movement
18 if (x > canvas.width - 30 || x < 0) vx = -vx;
19
20 // Draw
21 ctx.fillStyle = "#00FF41";
22 ctx.beginPath();
23 ctx.arc(x, canvas.height / 2, 30, 0, Math.PI * 2);
24 ctx.fill();
25
26 // Next frame
27 requestAnimationFrame(gameLoop);
28}
29
30requestAnimationFrame(gameLoop);
🔥

pro tip

Always use delta time for movement calculations. Multiplying by dt ensures objects move at the same speed regardless of frame rate. Never use fixed increments per frame — they will stutter on low FPS and speed up on high FPS.

Below is a live animation demo using the game loop pattern. The particles move with delta-time-based physics, bounce off walls, and leave a trail effect:

preview
Compositing & Shadows

Canvas compositing controls how new drawings blend with existing pixels. The globalCompositeOperation property selects the blending mode.

OperationDescription
source-overDraw new over old (default)
source-atopDraw new atop old, clip to existing
source-inShow new where old is opaque
source-outShow new where old is transparent
destination-overDraw new behind old
destination-atopKeep old where new is opaque
destination-inKeep old where new is opaque
destination-outErase old where new is drawn
lighterSum colors (additive blending)
copyReplace old with new (no blending)
xorExclusive OR of pixels
multiplyMultiply colors (darken)
screenInverse multiply (lighten)
overlayMultiply or screen based on base
Clipping Paths

clip() restricts all subsequent drawing to the current path area. Useful for masks, rounded corners, and scissor effects.

clipping.js
JavaScript
1// Create a circular clipping region
2ctx.beginPath();
3ctx.arc(150, 150, 100, 0, Math.PI * 2);
4ctx.clip();
5
6// All subsequent drawings are masked to the circle
7ctx.fillStyle = "#00FF41";
8ctx.fillRect(0, 0, 300, 300); // only visible inside circle
9
10// Save/restore to undo clipping
11ctx.save();
12ctx.beginPath();
13ctx.rect(50, 50, 200, 200);
14ctx.clip();
15// ... clipped operations ...
16ctx.restore(); // clipping removed
Shadows

Canvas supports drop shadows for all drawing operations. Shadow properties affect everything drawn after they are set.

shadows.js
JavaScript
1// Configure shadow
2ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
3ctx.shadowBlur = 15;
4ctx.shadowOffsetX = 5;
5ctx.shadowOffsetY = 5;
6
7// Draw shape with shadow
8ctx.fillStyle = "#00FF41";
9ctx.fillRect(50, 50, 100, 100);
10
11// Text shadow
12ctx.font = "bold 48px monospace";
13ctx.shadowBlur = 20;
14ctx.shadowColor = "#00FF4180";
15ctx.fillStyle = "#FFFFFF";
16ctx.fillText("GLOW", 50, 200);
17
18// Disable shadow
19ctx.shadowColor = "transparent";

best practice

Shadows are computationally expensive. Use them sparingly in animations — every shadow requires an additional offscreen render pass. For high-performance shadow effects, consider prerendering to an offscreen canvas.
preview
WebGL Basics

WebGL (Web Graphics Library) provides hardware-accelerated 3D rendering using the GPU. It is based on OpenGL ES and uses shaders written in GLSL. The API is significantly lower-level than Canvas 2D.

webgl.js
JavaScript
1// Initialize WebGL context
2const canvas = document.getElementById("glCanvas");
3const gl = canvas.getContext("webgl");
4if (!gl) {
5 console.error("WebGL not supported");
6}
7
8// WebGL uses the GPU via shader programs
9// Vertex shader: positions
10// Fragment shader: colors
11
12// Minimal clear-screen example
13gl.clearColor(0.05, 0.05, 0.12, 1.0); // dark bg
14gl.clear(gl.COLOR_BUFFER_BIT);
15
16// WebGL2 is also available with more features
17const gl2 = canvas.getContext("webgl2");
18
19// For most 2D rendering, Canvas 2D is faster to develop
20// Use WebGL for: 3D, complex particle systems, game engines

info

For advanced 3D rendering, consider using a library like Three.js or Babylon.js on top of WebGL. For 2D GPU-accelerated rendering with a simpler API, explore the OffscreenCanvas API and WebGL-based 2D libraries like PixiJS.
preview
Best Practices

HiDPI / Retina Displays

On high-DPI displays (devicePixelRatio > 1), a CSS-sized canvas will appear blurry. Scale the canvas buffer by devicePixelRatio and scale all drawing coordinates accordingly.

hidpi.js
JavaScript
1function setupHiDPICanvas(canvas, width, height) {
2 const dpr = window.devicePixelRatio || 1;
3 canvas.width = width * dpr;
4 canvas.height = height * dpr;
5 canvas.style.width = width + "px";
6 canvas.style.height = height + "px";
7 const ctx = canvas.getContext("2d");
8 ctx.scale(dpr, dpr);
9 return ctx;
10}
11
12const canvas = document.getElementById("myCanvas");
13const ctx = setupHiDPICanvas(canvas, 800, 600);
14// Now draw using logical coordinates (800x600)
15ctx.fillRect(10, 10, 100, 100); // crisp on retina

Performance

Canvas performance strategies for complex scenes:

performance.js
JavaScript
1// 1. Offscreen Canvas — render in worker threads
2const offscreen = canvas.transferControlToOffscreen();
3const worker = new Worker("renderer.js");
4worker.postMessage({ canvas: offscreen }, [offscreen]);
5
6// 2. Layer canvases — stack multiple canvases
7// Static layer: rendered once, never cleared
8// Dynamic layer: updated every frame (smaller area)
9
10// 3. Dirty rectangle tracking — only clear/redraw changed areas
11const dirtyRects = [];
12function markDirty(x, y, w, h) {
13 dirtyRects.push({ x, y, w, h });
14}
15function renderDirty() {
16 for (const rect of dirtyRects) {
17 ctx.clearRect(rect.x, rect.y, rect.w, rect.h);
18 // redraw only that area
19 }
20 dirtyRects.length = 0;
21}
22
23// 4. Batch path operations — minimize state changes
24ctx.beginPath();
25for (const p of particles) {
26 ctx.moveTo(p.x, p.y);
27 ctx.lineTo(p.x + p.w, p.y + p.h);
28}
29ctx.stroke(); // one draw call for all lines
30
31// 5. Avoid shadowBlur in animations
32// 6. Use integer coordinates for crisp lines
33// 7. Use requestAnimationFrame, not setInterval

Memory Management

Large canvases and frequent getImageData calls consume significant memory. Always release references when done.

memory.js
JavaScript
1// Create temporary offscreen canvases
2function createOffscreen(w, h) {
3 const c = document.createElement("canvas");
4 c.width = w;
5 c.height = h;
6 return c;
7}
8
9// Pre-render static elements to offscreen canvas
10const staticLayer = createOffscreen(400, 300);
11// ... render once ...
12
13// In animation loop, just blit the pre-rendered layer
14ctx.drawImage(staticLayer, 0, 0);
15// Then draw dynamic elements on top

best practice

Always call canvas.getContext("2d") only once. Creating multiple contexts on the same canvas is an error in most browsers. Cache the context reference and reuse it throughout your application.
Always handle HiDPI with devicePixelRatio scaling
Use requestAnimationFrame instead of setInterval/setTimeout
Batch draw calls — minimize state changes and beginPath/closePath pairs
Use offscreen canvases for static background layers
Avoid shadowBlur and globalCompositeOperation in hot loops
Keep canvas dimensions reasonable — 4K canvases use ~32MB of memory
Use integer coordinates to avoid sub-pixel anti-aliasing
Clear canvas with clearRect instead of resetting width/height
Detect context loss (especially for WebGL) and handle gracefully
Profile with the browser's built-in canvas inspector
$Blueprint — Engineering Documentation·Section ID: HTML-CANVAS·Revision: 1.0