canvas-2d
Use when making browser games or visualisations with the HTML Canvas 2D API and no engine — fixed logical resolution with scaling, game loop with fixed time step, pointer input for mouse and touch, dr
SKILL.mdskills/tech/canvas-2d/SKILL.md
GitHubCanvas 2D games
Default: one <canvas>, a fixed logical resolution (e.g. 960×540) scaled to the window, a fixed-step update loop, Pointer Events, everything drawn in code (no image assets) unless the project allows them.
Resolution and scaling
const W = 960, H = 540;
function resize() {
const scale = Math.min(innerWidth / W, innerHeight / H);
const dpr = devicePixelRatio || 1;
canvas.style.width = `${W * scale}px`;
canvas.style.height = `${H * scale}px`;
canvas.width = Math.round(W * scale * dpr);
canvas.height = Math.round(H * scale * dpr);
ctx.setTransform(scale * dpr, 0, 0, scale * dpr, 0, 0); // draw in logical units
}
- Convert pointer positions back to logical units with the canvas's
getBoundingClientRect()and the same scale. - Recompute on
resizeand orientation change; support both landscape and portrait if mobile matters.
Loop
requestAnimationFramefor rendering; accumulate elapsed time and runupdate(FIXED_DT)in a while loop; cap the accumulator after a tab switch.- Keep update deterministic (seeded RNG, fixed step) so automated "bench" players can measure difficulty.
Making shape-only art look finished
- Silhouette first: a character must be recognisable as a filled shape. Then add 2–3 tones (base, shadow, highlight) and an outline.
- Use rounded rectangles (
ctx.roundRect), consistent stroke widths and a limited palette. - At small sizes switch to simpler drawings (level of detail): full detail above ~12 px, a rounded block with an outline at 8–12 px, a dot in team colour below that.
- Give the UI hierarchy: one large number, smaller labels, panels with padding and rounded corners.
- Add "juice" with restraint: hit pause (a few frames), screen shake, particles, floating numbers, and a sound for each meaningful action.
- Separate overlapping units visually (draw-time offsets) rather than moving them in the simulation, so game logic and tests are unaffected.
Performance
- Batch by state: set
fillStyleonce, draw many paths, avoidsave/restorein inner loops. - Pre-render static backgrounds to an offscreen canvas and
drawImageit each frame. - Avoid
shadowBlurandfilterin hot paths; they are expensive. - Text: cache measured widths;
fillTextwith many fonts per frame is slow. - Measure with a stress scene (thousands of units) and keep the frame time under budget on modest hardware.
Verification loop
- Unit tests for update logic (no canvas).
- Playwright: open each screen, assert no console errors, take screenshots at desktop and phone sizes.
- A bench that runs the simulation headless across seeds and asserts difficulty ranges.
Pitfalls
- Blurry output: forgetting
devicePixelRatio. - Touch scrolling and zooming steal input:
touch-action: noneon the canvas. - Fonts: a web font may not be loaded on the first frame; wait for
document.fonts.readyor use system fonts. - Sub-pixel lines look fuzzy; align 1 px strokes to
x + 0.5. clearRectwith a transform clears in transformed space; reset the transform or clear the logical rectangle.