To make a small HTML5 game, create a page with a canvas, keep game objects in JavaScript, and use requestAnimationFrame() to update and redraw the scene. Calculate movement from the callback’s timestamp rather than moving a fixed amount per frame, so the game behaves consistently across different display refresh rates.
What a browser game needs
A compact browser game has three basic parts: an HTML page, JavaScript that stores and updates the game state, and a way to display the scene. Canvas 2D is a convenient starting point when you want to draw a unified scene with moving objects. It is not the only option: MDN’s guide to drawing graphics also discusses SVG and WebGL.
As an Amazon Associate I earn from qualifying purchases.
Canvas drawings are pixels on a drawing surface, not separate DOM elements. Keep positions, velocities, and other object data in JavaScript so you can update them and draw the scene again each frame. If individual objects need to remain directly addressable as page elements, DOM-based graphics may be a better fit.
How to make a game loop with requestAnimationFrame()
window.requestAnimationFrame(callback) asks the browser to call a function before a repaint. It is a one-shot request: the callback must request another frame to keep the animation running. The browser’s callback timing generally follows the display’s refresh rate, which can vary by device. Browsers commonly pause callbacks in background tabs and hidden iframes. See MDN’s requestAnimationFrame() reference.
#1 Best Overall
This minimal example moves a square across a canvas. Save it as an HTML file and open it in a browser:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canvas game loop</title>
</head>
<body>
<canvas width="640" height="360" aria-label="A square moving across the game area">
A square moves from left to right across the game area.
</canvas>
<script>
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const player = { x: 40, y: 40, speed: 180 };
let previousTime;
let frameId;
let running = true;
function frame(timestamp) {
if (!running) return;
const deltaSeconds = previousTime === undefined
? 0
: Math.min((timestamp - previousTime) / 1000, 0.05);
previousTime = timestamp;
player.x += player.speed * deltaSeconds;
if (player.x > canvas.width) player.x = -24;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(player.x, player.y, 24, 24);
frameId = requestAnimationFrame(frame);
}
frameId = requestAnimationFrame(frame);
function stopGame() {
running = false;
cancelAnimationFrame(frameId);
}
</script>
</body>
</html>
The first callback only establishes a starting timestamp, so the initial elapsed time is zero. On later callbacks, the code converts the timestamp difference from milliseconds to seconds, multiplies it by the player’s speed, clears the previous drawing, and paints the updated position. The example’s 0.05-second maximum is an implementation choice that limits a large movement jump after a delayed frame; it is not a browser requirement.
Rank #2
Why movement should use elapsed time
If you add a fixed distance on every callback, an object’s speed depends on how often callbacks occur. A display refreshing at 120 Hz can receive more callbacks per second than one refreshing at 60 Hz, so fixed-per-frame movement can run at different speeds. Instead, express speed in units per second and multiply by elapsed seconds: distance = speed × deltaSeconds.
A background tab may stop receiving animation callbacks. When the page becomes active again, the timestamp difference can be much larger than a typical frame interval. Capping elapsed time, as in the example, can prevent a sudden jump. The cap’s value should suit the game; it trades simulating the entire delay for avoiding an unexpectedly large update.
How to add input and build the scene
Keep input handling separate from drawing where practical. Event handlers can record which keys or pointer buttons are held; the game loop then reads that state when updating the player. This keeps the loop responsible for applying game rules and rendering, rather than drawing directly in response to each input event.
- Create the surface: Add a
<canvas>element and obtain its 2D context withcanvas.getContext("2d"). - Represent the world: Store each player, obstacle, or other object as JavaScript data, such as position and speed.
- Record input: Register keyboard, pointer, or touch event handlers that update input state.
- Start the loop: Request a frame with
requestAnimationFrame(frame). - Update and draw: Calculate elapsed time from the callback timestamp, update game state, clear or cover the old frame, and draw the current scene.
- Continue only while active: Request another frame during gameplay, and retain the request ID for a pause, game-over state, or teardown.
- Load images before drawing them: If the game uses image assets, wait for each image’s load event before calling
drawImage(). See MDN’s Canvas drawing guide.
How to stop or pause the loop
Store the ID returned by the most recent requestAnimationFrame() call. Pass that ID to cancelAnimationFrame(frameId) to cancel the scheduled callback. In the example, the running flag also prevents the callback from updating or drawing if it is invoked after the game has been stopped.
Rank #4
To resume after a pause, reset previousTime before requesting a new frame. Otherwise, the first resumed callback may calculate elapsed time across the entire pause and move objects too far. The loop should schedule another callback only when the game is meant to run. MDN’s anatomy of a video game explains the game-loop structure and timing trade-offs.
When to consider a different rendering or update approach
Canvas 2D is a practical default for a small game whose objects are redrawn together. SVG or DOM elements can suit scenes where individual objects need to be addressable, while WebGL may be appropriate for graphics workloads that warrant its additional complexity. The right choice depends on scene complexity, accessibility and interaction needs, performance requirements, and what you already know.
Best Value
Updating and drawing once per animation callback is the simplest loop and is suitable for many basic games. Games with physics or deterministic simulation may need fixed-step updates separated from rendering. That design can make simulation timing more predictable, but it adds complexity; choose it based on the game’s requirements rather than adding it automatically.
Make canvas content understandable and usable
Canvas content is not automatically exposed to screen readers like ordinary DOM content. Provide an accessible description and equivalent controls or information for meaningful game content. The example includes an aria-label and fallback text; for a game whose meaning changes as it runs, update the description or provide an equivalent accessible representation. See MDN’s Canvas accessibility guidance.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →




