The frame loop is where browser games go wrong
A game can have perfect mechanics and still feel unfair if the loop is written carelessly. Most of the work is in the timing.
Almost every complaint about a browser game feeling wrong — unfair, floaty, unresponsive, or occasionally impossible — traces back to the loop that drives it. The mechanics can be correct and the game can still be unpleasant, because the loop is what turns numbers into motion and motion into something a player can judge.
One loop, driven by the browser
The loop is a single requestAnimationFrame callback that calls itself. The browser decides when to run it, which is the point: it runs in step with the display refresh and it stops when the tab is hidden. A setInterval would keep running in the background, burning battery to compute frames nobody will see.
Time, not frames
Movement is expressed per second and multiplied by the time the last frame took. This is what makes a game behave the same on a 60 Hz phone and a 120 Hz one. If positions were advanced per frame instead, the game would run at double speed on the faster screen, which is the single most common bug in a first browser game.
Cap the delta
The trap in that approach is that the time between frames is not always small. Switch tabs, lock the phone, take a garbage-collection pause, or let the browser deprioritise a background window, and the next delta can be seconds rather than milliseconds. A game that multiplies an unclamped delta by a velocity will teleport everything on screen, and a fast object can pass straight through a wall in a single step.
So the delta is capped — in these games at fifty milliseconds. Past that, the frame is treated as if it took fifty milliseconds and the rest of the lost time is simply discarded. The game briefly runs in slow motion instead of falling apart, which is the correct trade for anything with collisions.
Pause is a state, not an absence of frames
Pausing is easy to get subtly wrong: stop calling the loop and the game freezes, but the elapsed time keeps accumulating, so the first frame after a resume applies every second that passed while the player was away. The fix is to keep drawing while paused but skip the update, and to reset the timestamp on resume.
A game running inside a host environment has to take its pause information from that host rather than from the page's visibility, because the host may freeze the page without the document ever reporting itself hidden. Games here listen to both, and outside a sandboxed host they also treat the window losing focus as a pause.
Tell the host you have started
Embedded platforms generally want to know when the game has actually rendered its first frame, so that they can hide their own loading state at the right moment. That call is made once, inside the loop, after the first draw — not when the script runs, because a script that has run has not necessarily drawn anything yet.
The order inside a frame
The loop here does four things in a fixed order: update if the game is in play and not paused, draw, notify the host on the very first frame, and request the next frame. Keeping the order fixed matters more than it looks — input read after the update is a frame late, and drawing before the update shows the state the player has already left behind.
None of this is difficult. It is just the part that nobody notices until it is wrong.
Want to go deeper?
Building something for the browser? Send us a link and we will tell you honestly whether it fits.