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>
ApproachMovement per UpdateResult at 30fps vs 144fps
Fixed stepx += 3Object moves roughly 5x slower on the 30fps machine
Delta timex += speed * deltaTimeObject covers the same distance per second on both machines
Note: Clamp deltaTime to a small maximum (for example 0.1 seconds) before using it. Without a cap, switching away from the browser tab and back can produce one giant deltaTime on resume, which can teleport a fast-moving object clean through a wall it should have collided with.
  • 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.