Graphics The Game Loop
Every animated canvas program is built on a repeating cycle of clearing, updating, drawing, and scheduling the next frame — and delta time is what keeps that cycle running at a consistent speed on every device.
What a Game Loop Actually Does
Every animated canvas program, from a simple bouncing ball to a full game, is built around one repeating cycle: clear the screen, update the state of everything in the scene, draw the new state, then schedule the next repetition. That cycle is the game loop, and almost everything else in game programming builds on top of it.
- Clear the canvas — erase last frame's drawing.
- Update — move objects, check input, apply physics, advance timers.
- Draw — render every object in its new position.
- Request the next frame — ask the browser to call the loop again before the next repaint.
Example
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="game" width="480" height="320"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
function update() {}
function draw() {}
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
update();
draw();
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
</script>
</body>
</html>Why requestAnimationFrame Instead of setInterval
requestAnimationFrame asks the browser to call your function right before its next repaint, so your drawing stays in sync with the display's own refresh rate instead of fighting it. It also automatically pauses when the tab is in the background, saving battery and CPU, and it hands your callback a timestamp for free. setInterval does none of this — it fires on a fixed timer regardless of whether the browser is ready to paint, and keeps running even on a hidden tab.
The Trouble With Fixed-Step Movement
If you move an object by a fixed number of pixels on every call to update(), its on-screen speed depends entirely on how often the loop runs. A player on a 144Hz monitor sees the object cross the screen faster than a player on a 30Hz one, simply because update() is being called more often per second on the faster machine — the game would run at different speeds depending on hardware.
Example
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="game" width="480" height="320"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const ball = { x: 50 };
function draw() {}
let lastTime = 0;
const speed = 200; // pixels per second
function gameLoop(timestamp) {
const deltaTime = (timestamp - lastTime) / 1000; // seconds since last frame
lastTime = timestamp;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ball.x += speed * deltaTime;
draw();
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
</script>
</body>
</html>- clearRect wipes the previous frame so old drawings don't smear.
- update() advances positions using speed * deltaTime, so motion is frame-rate independent.
- draw() renders every object at its new position.
- requestAnimationFrame schedules the next iteration and hands your callback a timestamp to compute the next deltaTime.