Canvas 2D / crib sheet

ctx = canvas.getContext('2d')

core

Setup

const canvas = document.getElementById('c');
const ctx    = canvas.getContext('2d');

// Match canvas pixels to display size
canvas.width  = canvas.offsetWidth  * devicePixelRatio;
canvas.height = canvas.offsetHeight * devicePixelRatio;
ctx.scale(devicePixelRatio, devicePixelRatio);

Origin (0,0) is top-left. X→ right, Y↓ down.

rect

Rectangles

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

No beginPath() needed — these are standalone shortcuts.

path

Paths

ctx.beginPath();          // always start here
ctx.moveTo(x, y);          // move without drawing
ctx.lineTo(x, y);          // line to point
ctx.arc(x, y, r, start, end); // angles in radians
ctx.arcTo(x1,y1, x2,y2, r); // rounded corner
ctx.bezierCurveTo(cp1x,cp1y, cp2x,cp2y, x,y);
ctx.quadraticCurveTo(cpx,cpy, x,y);
ctx.rect(x, y, w, h);       // rectangle as path
ctx.closePath();            // line back to start

ctx.fill();                 // fill current path
ctx.stroke();               // stroke current path
ctx.clip();                 // use path as clipping mask

Full circle: arc(x, y, r, 0, Math.PI * 2)

0 → 3 o'clock
Math.PI/2 → 6
Math.PI → 9
Math.PI*1.5 → 12
Math.PI*2 → full circle
style

Style — set before fill / stroke

ctx.fillStyle         = '#e8ff5a';   // colour, rgba(), gradient…
ctx.strokeStyle       = 'rgba(255,255,255,0.4)';
ctx.lineWidth         = 2;
ctx.lineCap           = 'round';   // butt | round | square
ctx.lineJoin          = 'round';   // miter | round | bevel
ctx.globalAlpha       = 0.8;       // 0–1
ctx.globalCompositeOperation = 'source-over';

// Gradients
const g = ctx.createLinearGradient(x0,y0, x1,y1);
g.addColorStop(0, 'red');
g.addColorStop(1, 'blue');
ctx.fillStyle = g;

// Shadow
ctx.shadowColor   = 'rgba(0,0,0,0.5)';
ctx.shadowBlur    = 8;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
text

Text

ctx.font         = 'bold 16px sans-serif';
ctx.textAlign    = 'left';   // left|center|right|start|end
ctx.textBaseline = 'top';    // top|middle|alphabetic|bottom

ctx.fillText  ('Hello', x, y);
ctx.strokeText('Hello', x, y);
ctx.fillText  ('Hello', x, y, maxWidth); // optional clamp

const m = ctx.measureText('Hello');
m.width;              // rendered width in px
m.actualBoundingBoxAscent;  // useful for precise layout
images

Images

// source can be: img, canvas, video, ImageBitmap…
ctx.drawImage(src, dx, dy);
ctx.drawImage(src, dx, dy, dw, dh);        // scaled
ctx.drawImage(src, sx,sy,sw,sh, dx,dy,dw,dh); // crop→scale

// Pixel access
const px = ctx.getImageData(x, y, w, h);
// px.data — Uint8ClampedArray [r,g,b,a, r,g,b,a, …]
ctx.putImageData(px, x, y);

Must be loaded first — use the load event or await createImageBitmap().

transforms

Transforms

ctx.save();                    // push state onto stack
  ctx.translate(x, y);         // move origin
  ctx.rotate(angleInRadians);   // rotate around origin
  ctx.scale(sx, sy);            // scale (1 = normal)
  ctx.transform(a,b,c,d,e,f);  // raw matrix multiply
  ctx.setTransform(a,b,c,d,e,f);// replace matrix entirely
  // … draw stuff …
ctx.restore();                  // pop state from stack

Always wrap in save/restore so transforms don't bleed into later drawing. Rotate around a point by translating to it first, then rotating, then drawing centred on (0,0).

animation

Animation Loop

let rafId;
let lastTimestamp = performance.now();

function draw(timestamp) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // timestamp is ms since page load — use for time-based motion
  // deltaT can be used as a multiplier when things like speed
  // are stored as pixels per second
  const deltaT = (timestamp - lastTimestamp) * 0.001; // seconds
  lastTimestamp = timestamp;

  // … draw stuff …

  rafId = requestAnimationFrame(draw);
}

rafId = requestAnimationFrame(draw);

// To stop:
cancelAnimationFrame(rafId);

rAF syncs to display refresh (~60fps / 120fps). Prefer time-based values (sin(t), t * speed) over frame-count so motion is consistent at any refresh rate.

demo

Live — shapes, text, transforms & animation

Rotating square uses save/restore + translate + rotate. Arc uses beginPath + arc + fill. Sine wave uses moveTo + lineTo in a loop.

state

What save() / restore() preserves

The full drawing state is saved — not just the transform. This includes: fillStyle strokeStyle lineWidth lineCap lineJoin globalAlpha globalCompositeOperation font textAlign textBaseline shadowColor shadowBlur and the current clipping region. The stack can be nested as deep as needed.

gotchas

Common Gotchas

Forgot beginPath() — previous sub-paths get re-drawn / re-filled.
Blurry canvas on HiDPI — multiply width/height by devicePixelRatio, then scale ctx.
Image not drawing — it hasn't loaded yet; wait for the load event.
Styles bleeding — set fillStyle / strokeStyle inside save/restore blocks.
Rotating around wrong point — translate to the desired pivot first, then rotate.
Canvas size ≠ CSS sizecanvas.width sets pixel buffer; CSS sets display size.