Skip to content
Topic 3 of 5
Theory

Controls

Two ways to listen to the keyboard

In the previous DOM topic we reacted to keydown immediately: you press an arrow — the ball jumps one cell. For a game this is bad: while a key is held, keydown repeats with a delay (like letters in a text field), and the movement is jerky. And two keys at the same time (right + jump) can't be caught this way at all.

That's why in games keyboard events only remember which keys are held right now, and the game loop looks at this every frame and moves the hero smoothly.

A set of held keys

Set is a collection without duplicates with fast add, delete and has. A perfect fit:

const keys = new Set();

document.addEventListener("keydown", (e) => keys.add(e.key));
document.addEventListener("keyup", (e) => keys.delete(e.key));

// in update:
if (keys.has("ArrowLeft")) player.x -= 200 * dt;
if (keys.has("ArrowRight")) player.x += 200 * dt;

Repeated keydown events while holding don't get in the way: adding to a Set something that's already there changes nothing.

Arrows and space scroll the page by default. To prevent that during the game:

document.addEventListener("keydown", (e) => {
  keys.add(e.key);
  if (e.key.startsWith("Arrow") || e.key === " ") e.preventDefault();
});

One more small thing: if the window loses focus while a key is held, keyup won't arrive, and the hero will keep running by itself. The fix: window.addEventListener("blur", () => keys.clear());.

Direction and speed

It's convenient to first compute the direction −1, 0 or 1, and then multiply it by the speed:

let dx = 0;
if (keys.has("ArrowLeft")) dx -= 1;
if (keys.has("ArrowRight")) dx += 1;   // both held → 0, we stand still
player.x += dx * SPEED * dt;

Moving diagonally turns out faster (√2 ≈ 1.41 times), because two movements add up. Arcade games often forgive this, while precise games normalize the direction — they divide dx and dy by the length of the vector.

Not going past the edge

To keep the hero from running off the screen, its coordinate is clamped between a minimum and a maximum:

const clamp = (v, min, max) => Math.max(min, Math.min(max, v));

player.x = clamp(player.x, 0, canvas.width - player.w);
player.y = clamp(player.y, 0, canvas.height - player.h);

The maximum is canvas width − hero width, because x is the left edge, and the hero's right edge is w pixels further.

Jumping

A jump is an instant upward velocity (a negative vy, since y grows downward), which gravity then cancels out:

if (keys.has(" ") && player.onGround) {
  player.vy = -400;
  player.onGround = false;
}
player.vy += GRAVITY * dt;
player.y += player.vy * dt;

if (player.y >= GROUND) {      // fell onto the ground
  player.y = GROUND;
  player.vy = 0;
  player.onGround = true;
}

The onGround flag forbids jumping in the air. The jump height depends on the initial velocity and gravity: more gravity — a lower, "heavier" jump. Tuning these numbers is part of game design.

Mouse

The mouse on a canvas works the same way, through events. Only the mouse coordinates are relative to the window, while we need coordinates inside the canvas:

canvas.addEventListener("click", (e) => {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  console.log("click at canvas point", x, y);
});

Summary

  • Keyboard events only record state: keys.add / keys.delete; decisions are made in update.
  • preventDefault() for arrows and space, keys.clear() when focus is lost.
  • Direction −1/0/1 × speed × dt; clamp coordinates with Math.max / Math.min.
  • Jump: a negative vy + gravity + the onGround flag.

Which Keys Are Held

Easy+10 XP

Create a set keys (new Set()) and keep it up to date: on keydown on document add e.key to it, on keyup — remove it. Write a function isDown(key) that tells whether the key is held right now.

pressed →         isDown("ArrowRight") → true
released →        isDown("ArrowRight") → false

Connect the connector to solve tasks

Task code runs on your own computer — faster and more reliable than in the browser. This needs the free CodeWithMe connector app. You only connect it once.

Looking for the connector on this computer…

  1. 1
    Download the connector for your system (Windows, macOS or Linux).Download the connectorOn the download page pick the file with your system in its name, e.g. windows-amd64.
  2. 2Run it and choose a folder for your solutions. It will install Python and Node.js there by itself when needed.
  3. 3
    Enter below the 6-digit code shown in the connector window (“Code for CodeWithMe”). If the browser asks about local network access — allow it.

The connector is open source (on GitHub), works only on your computer and runs code only after you enter the pairing code.