Keyboard input without stuck keys
Track which keys are held in a Set and read it during update, instead of moving things inside event handlers. Use `e.code` (physical key) so WASD works on any keyboard layout. Clear the set on blur: if the player alt-tabs while holding a key, keyup never fires and the character walks forever.
const held = new Set<string>();
const GAME_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Space"];
addEventListener("keydown", (e) => {
if (GAME_KEYS.includes(e.code)) e.preventDefault(); // stop page scrolling
held.add(e.code);
});
addEventListener("keyup", (e) => held.delete(e.code));
addEventListener("blur", () => held.clear()); // alt-tab never sends keyup
// In update():
const right = held.has("ArrowRight") || held.has("KeyD");
const left = held.has("ArrowLeft") || held.has("KeyA");
player.dirX = Number(right) - Number(left); // -1, 0, or 1Related
Fixed-timestep game loop
Run physics in fixed steps and render as often as the screen allows. An accumulator carries leftover time between frames, so a 60 Hz and a 144 Hz monitor simulate the same game. Clamp the frame time so a backgrounded tab doesn't try to catch up on minutes of updates. Phaser, Godot, and Unity do this for you; write it yourself when you're on plain Canvas or a renderer like PixiJS.
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.