130 Widgets

Semi-random thoughts and tales of tinkering

Lesson 3: The Animation Loop

So far, our drawings have been still pictures. But games need movement! In this lesson, you'll learn the secret behind all video game animation — the game loop. 🎬

How Animation Works

Have you ever made a flip-book? You draw a slightly different picture on each page, and when you flip through them fast, it looks like movement!

Games work the exact same way. The computer draws a picture (called a frame), then erases it, makes tiny changes, and draws it again — about 60 times per second!

Imagine a whiteboard. You draw a ball on the left side. Then you erase it, and draw it a tiny bit to the right. Erase, draw, erase, draw — 60 times per second! To your eyes, the ball looks like it's moving smoothly across the board.

The Game Loop

The game loop is a function that runs over and over, forever. Each time it runs, it does three things:

  1. Clear the screen (erase the old drawing)
  2. Update the game (move things, check rules)
  3. Draw everything in its new position
function gameLoop() {
  // 1. Clear the screen
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 2. Update (move things)
  ballX = ballX + 2;  // Move the ball 2 pixels to the right

  // 3. Draw
  ctx.fillStyle = 'red';
  ctx.beginPath();
  ctx.arc(ballX, ballY, 20, 0, Math.PI * 2);
  ctx.fill();

  // Ask the browser to call gameLoop again on the next frame
  requestAnimationFrame(gameLoop);
}

// Start the loop!
gameLoop();

🔄 requestAnimationFrame

requestAnimationFrame(gameLoop) tells the browser: "When you're ready to draw the next frame (about 1/60th of a second from now), please call my gameLoop function again."

This creates an endless loop: gameLoop calls requestAnimationFrame, which calls gameLoop again, which calls requestAnimationFrame again... forever! This is what keeps the game running.

See It In Action!

Here's a ball that bounces around the screen using our game loop:

A bouncing ball! It reverses direction when it hits a wall.

How the Bouncing Works

The ball has a position (x and y) and a speed (speedX and speedY). Each frame, we add the speed to the position. When the ball hits a wall, we reverse its speed (multiply by -1):

let ballX = 100, ballY = 100;
let speedX = 3, speedY = 2;

function gameLoop() {
  // Clear
  ctx.clearRect(0, 0, 600, 250);

  // Update - move the ball
  ballX += speedX;
  ballY += speedY;

  // Bounce off walls
  if (ballX < 20 || ballX > 580) speedX = speedX * -1;
  if (ballY < 20 || ballY > 230) speedY = speedY * -1;

  // Draw the ball
  ctx.fillStyle = '#ff6b6b';
  ctx.beginPath();
  ctx.arc(ballX, ballY, 20, 0, Math.PI * 2);
  ctx.fill();

  requestAnimationFrame(gameLoop);
}

gameLoop();

📐 New Drawing Method: arc()

ctx.arc(x, y, radius, startAngle, endAngle) draws a circle!

  • x, y — Center position of the circle
  • radius — How big the circle is
  • 0, Math.PI * 2 — Draws a full circle (from 0 to 360 degrees)

We use ctx.beginPath() before the arc and ctx.fill() after to actually draw it.

Making Multiple Things Move

Let's make something that looks more like a game — a moving character with a scrolling background! We can have a character walking across a scene with blocks:

A character walking across a block landscape! (It loops around)

let characterX = 0;

function gameLoop() {
  ctx.clearRect(0, 0, 600, 250);

  // Sky
  ctx.fillStyle = '#87ceeb';
  ctx.fillRect(0, 0, 600, 250);

  // Move the character to the right
  characterX += 1.5;
  if (characterX > 620) characterX = -30;  // Loop around

  // Draw ground blocks
  for (let x = 0; x < 600; x += 32) {
    drawBlock(x, 192, 'grass');
    drawBlock(x, 224, 'dirt');
  }

  // Draw the character at its current position
  drawPlayer(characterX, 136);

  requestAnimationFrame(gameLoop);
}
💡 The Clear Step is Important! Try removing the ctx.clearRect() line in your own code. You'll see the old drawings pile up because we never erase them. Always clear first!

Keeping Track of Time

Sometimes we want to know exactly how fast our game is running. The number of frames drawn per second is called FPS (Frames Per Second). Most games aim for 60 FPS — that's 60 drawings every second!

let lastTime = 0;
let fps = 0;

function gameLoop(currentTime) {
  // Calculate FPS
  fps = Math.round(1000 / (currentTime - lastTime));
  lastTime = currentTime;

  // ... rest of game loop ...

  // Show FPS on screen
  ctx.fillStyle = '#fff';
  ctx.font = '14px Consolas';
  ctx.fillText('FPS: ' + fps, 10, 20);

  requestAnimationFrame(gameLoop);
}

🏆 Challenges — Try These!

  • Change the ball's speed — what happens if you make speedX = 8?
  • Add a second ball with different speeds and colors
  • Make the character move in the opposite direction (hint: use -1.5 instead of 1.5)
  • Try making the ball leave a trail — remove the clearRect and see what happens!
  • Can you make the ball change color each time it bounces? (hint: change fillStyle when speed reverses)

📝 What We Learned

  • Animation = drawing, erasing, changing, drawing again — 60 times per second
  • The game loop is a function that repeats forever
  • requestAnimationFrame tells the browser to call our function again
  • clearRect erases the canvas before each new frame
  • We move things by changing their position variables a little each frame
  • Bouncing = reversing speed when hitting a boundary