BROWSER GAME BASICS
How Browser Games Work: An Introduction to Canvas and the Game Loop
Press a button and the craft moves; enemies and bullets cross the screen; a moment later, the result of a collision appears. Behind these actions is a system that repeats input, calculation, and drawing in short cycles.
Published: July 14, 2026
Redrawing the same screen many times
Browser games often use the HTML <canvas> element for display. Canvas is an area where JavaScript can draw rectangles, images, text, and other elements at specified coordinates. For example, if the player craft is drawn at x-coordinate 100 and y-coordinate 200, then at x-coordinate 104 on the next update, it appears to have moved to the right.
A typical sequence is “read input, update object positions, check collisions, clear the screen, and draw it again.” One pass through this sequence is called a game loop. On a 60 Hz display, preparing a new image about every 16.7 milliseconds produces smooth motion. The loop will not always run 60 times per second, however. Its timing changes with device performance and the state of other tabs, so movement should not depend only on the number of frames.
Synchronizing updates with requestAnimationFrame
requestAnimationFrame calls a specified function when the browser is ready to draw the next frame. A simple structure looks like this.
let previous = performance.now();
function loop(now) {
const dt = Math.min((now - previous) / 1000, 0.05);
previous = now;
update(dt);
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
dt is the number of seconds elapsed since the previous frame. If the speed is 240 pixels per second, add 240 * dt to the position. Even if the rate falls to 30 fps, the object travels almost the same distance over the same real time. In this example, dt is capped at 0.05 seconds. This prevents the game from calculating several seconds at once when a tab is restored and sending the player craft off-screen.
Distinguishing a key press from a held key
For keyboard input, listen for keydown and keyup, and record the currently held keys in a set. Check held keys continuously for movement, but process menu confirmation and bombs only at the moment a key is pressed. This avoids unintended repeated activation.
- Horizontal movement: Calculate direction from the left and right key states on every frame.
- Shots: Set a fixed firing interval and preserve it while the button is held.
- Pause: Change state only once when the button is pressed.
For touch controls, convert the finger position into coordinates within the Canvas. If CSS scales the display, its on-screen width differs from the Canvas's internal width. The key is to obtain the display area with getBoundingClientRect() and apply the ratio to the internal size. The controls must also be released when a finger leaves the screen or the device rotates.
Keeping collision detection simpler than the artwork
Comparing every image outline pixel by pixel is expensive and also makes the boundary difficult for players to read. Instead, use rectangle-to-rectangle or circle-to-circle tests. For circles, check whether the distance between their centers is less than the sum of their radii. Comparing the squared distance with the squared sum of the radii avoids calculating a square root and costs less.
In shooting games, the player craft's hitbox may be smaller than its artwork. Its size alone is not the important point; players need to understand where its center is. A dot at the center of the craft, or a hit response aligned with the collision position, keeps the visual information consistent with the calculation.
As the number of objects grows, remove off-screen bullets from the array. Comparing 1,000 bullets against 100 enemies exhaustively requires 100,000 checks. The play area can also be divided into a grid so that only nearby objects are compared. For a small game, however, it is easier to verify the implementation by first removing unnecessary objects and using simple geometric tests.
Separating game states
If the title screen, active play, pause, and result screen are all packed into the same process, errors can occur, such as enemies continuing to move after game over. Make the current state explicit with values such as title, playing, and result, and limit input and updates according to the state.
Separate the responsibilities of in-game data as well. The player craft can hold its position, speed, and remaining lives; enemies their type, health, and action time; and bullets their position, radius, and owner. This makes the system easier to follow. The score display should only read a calculated result, rather than adding points from the drawing process. The rules then remain unchanged even if the number of rendered frames changes.
Saving small amounts of data with localStorage
High scores and settings can be stored as strings in localStorage. They can be read on a later visit in the same browser on the same site, with no server required. Convert objects to strings with JSON.stringify, and use JSON.parse when reading them.
const data = { highScore: 12800, volume: 0.6, version: 1 };
localStorage.setItem("game-save", JSON.stringify(data));
When loading, account for missing or damaged data and return to initial values with try...catch. Adding a version number to the storage format makes future updates easier when fields are added.
localStorage is not a place for confidential information. JavaScript on the page can read it, and it can be lost when browser site data is cleared, during private browsing, or through device failure. It suits high scores, but important records that cannot otherwise be restored need another safeguard, such as an export feature. Users should also be told that it does not synchronize automatically between devices.
A process for checking a small prototype
- Draw one player craft on the Canvas and move it with the keyboard.
- Constrain its coordinates so it cannot cross the edge of the screen.
- Add one type of bullet and remove it when it leaves the screen.
- Add circle-to-circle collisions and show the collision areas with outlines.
- Separate the title, active play, and result states.
- Finally, save the high score and confirm that reloading restores it.
Rather than adding dozens of enemy types at the outset, stabilize one input and one collision first, then expand. This makes causes easier to isolate. During development, temporarily display coordinates, elapsed time, and hitboxes, and confirm that the visible motion agrees with the internal values.
Related links
About this article
Written by rin0420, the developer of the games and tools published on this site. The screenshots and figures shown here were captured from the live versions by using them directly.
Published July 14, 2026 / Last updated July 25, 2026 (screenshots added)