JavaScript Canvas Table
| Piece | What it does | Field note |
|---|---|---|
getContext('2d') | The drawing API handle | One context per canvas - calling it twice returns the same object |
fillRect / strokeRect | Instant rectangles | Paint immediately - no path needed for rectangles |
beginPath + arc + fill | Circles and curves | Path is a blueprint until fill() or stroke() commits it |
drawImage | Blit images and canvases | Another canvas is a valid source - layered scenes without DOM |
canvas.width = canvas.width | The clear-rect idiom | Resizing ALSO wipes - and setting CSS size alone stretches pixels |
requestAnimationFrame loop | The animation cycle | Clear, update, draw - one frame per repaint (60fps target) |
toDataURL / toBlob | Export as PNG | toBlob is async and memory-friendlier - the download path |
devicePixelRatio scaling | Retina sharpness | Canvas = css size x dpr, then ctx.scale(dpr, dpr) - or stay blurry |
Canvas is an immediate-mode bitmap: every draw call paints pixels now, and the canvas remembers nothing - no scene graph, no objects to move later, just the raster. Animation is therefore a manual cycle (clear, update state, redraw) run inside requestAnimationFrame's frame slot.
Bottom line: canvas sizing has TWO dimensions that are easy to conflate - width/height attributes set the bitmap resolution, CSS size sets the displayed box, and setting only the CSS size silently stretches or shrinks the pixels. The retina fix is one line of math: bitmap = css size x devicePixelRatio, then ctx.scale(dpr, dpr), and every coordinate stays in CSS units.
The honest part: canvas trades the DOM's retained convenience for raw throughput. Ten thousand moving dots is canvas territory (the DOM would drown); a form with labels is DOM territory (canvas would mean rebuilding accessibility from scratch). Canvas content is invisible to screen readers and unselectable by design - it is paint, not markup.
How to use
- Scale for the screen once: canvas.width = cssWidth * devicePixelRatio; canvas.height = cssHeight * dpr; ctx.scale(dpr, dpr) - then draw in CSS pixels forever.
- Animate with the frame slot: function tick() { ctx.clearRect(...); update(); draw(); requestAnimationFrame(tick); } - clear-update-draw, one pass per repaint.
- Export the drawing: canvas.toBlob(b => downloadBlob(b)) for PNG saves - async and memory-friendlier than the synchronous toDataURL string.
Frequently asked questions
What does immediate mode mean, practically?
The canvas holds no objects - only pixels. Draw a rectangle and the API does not remember it; to move it, you repaint the frame from scratch with the rectangle elsewhere. That is why the animation loop is clear, update, draw: the 'scene' lives in YOUR state variables, and each frame is a full re-render. The upside is speed and simplicity at scale (ten thousand particles is one loop over an array, no per-object DOM nodes); the downside is that hit-testing, z-ordering and updates are all manual - you are writing a tiny renderer, and libraries like Three.js exist because that is often worth abstracting.
Why did my canvas blur on a retina display?
The two-size mismatch. The bitmap (canvas.width/height) was probably set to the CSS pixel dimensions, so on a 2x screen the browser stretches 1 bitmap pixel across 2 device pixels - soft and slightly fuzzy. The fix multiplies the bitmap by devicePixelRatio and compensates with ctx.scale(dpr, dpr), so your code keeps drawing in CSS units while the underlying raster has 2x2 pixels per CSS pixel. Remember the trap door: ASSIGNING canvas.width resets the entire bitmap and state - so re-apply the scale after any resize.
Why did setting canvas.width erase my drawing?
By specification: changing the width or height attribute resets the bitmap to transparent black and clears all context state (transforms, styles, fonts). It is the nuclear clear - the idiom canvas.width = canvas.width exists because of it. Resize handlers should therefore follow the full sequence every time: set bitmap size, re-apply ctx.scale for dpr, redraw the scene. The silent cousin of this trap is CSS-only sizing: styling width/height in CSS changes the DISPLAYED box, stretching or squashing the existing bitmap instead of resampling it.
When is canvas the wrong tool compared to DOM or SVG?
When the drawing must be ADDRESSABLE. Screen readers, text selection, CSS hover states and per-element event handling all come free in DOM and SVG because nodes exist; canvas content is one flat bitmap with none of that - hit-testing is manual math, accessibility is manual ARIA on the canvas element at best. Use canvas for dense, dynamic, non-interactive pixels: particle fields, chart heatmaps, image manipulation, game sprites. Use SVG for a few hundred interactive, styleable shapes (icons, diagrams); use DOM for anything that is fundamentally UI. The moment someone asks 'how do I make THIS drawn rectangle clickable', the answer is either add hit-testing math or switch tools.