Sprite sheet animation on canvas
Pack animation frames side by side in one image and draw one slice at a time with the 9-argument `drawImage`. Pick the frame from elapsed time, not a frame counter, so the animation plays at the same speed on every monitor.
#sprites#animation#canvas
ts
const sheet = new Image();
sheet.src = "/hero-run.png"; // 6 frames in one row, each 32×32
const SIZE = 32;
const FRAMES = 6;
const FPS = 10; // animation speed, independent of refresh rate
function drawHero(ctx: CanvasRenderingContext2D, x: number, y: number, timeMs: number) {
const frame = Math.floor((timeMs / 1000) * FPS) % FRAMES;
ctx.drawImage(
sheet,
frame * SIZE, 0, SIZE, SIZE, // source slice
x, y, SIZE * 2, SIZE * 2, // destination (2× scale)
);
}Related
Crisp pixel-art scaling
Render at the game's native low resolution, then scale the canvas up with CSS. Turn off smoothing in both places, and scale by whole numbers — a 2.5× scale makes some pixels wider than others. Leftover space becomes letterboxing.
Frame-rate independent movement
Speeds should be per second, not per frame. Moving 4 px every frame means a 144 Hz player moves 2.4× faster than a 60 Hz one. Multiply by the elapsed time (delta time) instead. Good enough for simple games; use a fixed timestep when collisions or physics must be consistent.