Four parts and nothing else

Every game on this site is built from the same four components, and there is no fifth. No engine, no framework, no physics library, no asset pipeline.

  • A canvas element, which is a bitmap you draw into with JavaScript.
  • A loop driven by requestAnimationFrame, which redraws that bitmap sixty times a second.
  • An input layer that turns keyboard and pointer events into named actions.
  • localStorage, which keeps your records between visits.

That is genuinely the whole stack. The complexity in a browser game is not in the platform, it is in what you choose to do with roughly sixteen milliseconds per frame.

The canvas

A canvas is a rectangle of pixels with a drawing API. You call methods - fill a rectangle, stroke a path, draw an arc - and pixels change. It has no scene graph, no objects and no memory of what you drew: each frame you clear it and draw everything again.

That statelessness is the thing to internalise. A canvas does not know there is a character on it. Your code knows where the character is, and draws it there this frame. Next frame it draws it somewhere else. Nothing persists except your own data.

It also means the canvas is opaque to everything outside your code, including assistive technology. That is why every rule and control on this site exists as real HTML outside the canvas rather than drawn inside it.

Advertising keeps Candy Arcade free. Ads never cover gameplay or controls.

The loop

requestAnimationFrame asks the browser to call your function before the next repaint. Calling it again from inside that function produces a loop that runs at the display's refresh rate and pauses automatically when the tab is hidden.

The shape of a frame

1. Work out how long since the last frame, and clamp it - we cap at a thirtieth of a second so a stall cannot teleport anything through a wall. 2. Update game state by that amount of time. 3. Clear and redraw everything. 4. Clear one-frame input flags. 5. Update the HUD in the DOM. 6. Ask for the next frame.

The clamp in step one matters more than it looks. Without it, a frame that took two seconds - because the browser was busy - would advance the simulation by two seconds in one step, which moves fast objects straight through solid ones.

Time-based movement, not frame-based

The single most important correctness rule in a browser game: multiply everything by elapsed time.

Moving a character by three pixels per frame means it travels twice as fast on a 120 Hz display as on a 60 Hz one. Moving it by 180 units per second, multiplied by the frame's elapsed time, means it travels at the same speed on both.

Every velocity, acceleration, timer and duration in our engine is expressed per second and multiplied by delta time. It is a small discipline that removes an entire class of device-dependent bugs.

The input layer

Games should not attach their own event listeners. Ours go through one input module that maintains a set of named actions - left, right, up, down, action, alt - fed by both keyboard and pointer.

  • Multiple keys map to the same action, so arrows and WASD both work and space, Enter and up all trigger the primary action.
  • State queries (is left held?) and edge queries (was action just pressed?) are both available, because games need both.
  • One-frame flags are cleared at the end of each frame, so a press is detected exactly once.
  • Events from form fields are ignored, so typing in the search box never moves a character.
  • Pointer Events unify mouse, touch and pen behind one code path.

Centralising this means a game module contains zero input plumbing, and it means accessibility decisions - multiple keys per action, ignoring form fields - are made once rather than thirty times.

Storage

localStorage is a synchronous key-value store, scoped to the origin, persisting indefinitely and never transmitted to any server. It holds strings, so everything goes through JSON.

Two disciplines matter. Write rarely, because the API blocks the main thread - ours writes once at the end of a run. And wrap every access in a try/catch, because in private browsing, with storage blocked, or with a full quota, it throws rather than returning null.

Our storage module probes availability once at startup and falls back to an in-memory object if it fails, so the games still work and only the persistence is lost. Game pages then say so, because silently losing someone's high score is worse than telling them it will not be kept.

What we deliberately do not use

  • No game engine. At this scale an engine is more code than the games.
  • No framework. There is no complex UI state to manage; the HUD is five numbers.
  • No physics library. Our physics is about forty lines of integration and collision.
  • No image assets for gameplay. Everything is drawn from code, so there is no decode time, no texture memory and no blurriness at any resolution.
  • No audio files. Sound is synthesised with Web Audio, which also means no licensing questions.
  • No build step in the shipped site. The HTML is generated, but what ships is plain files.

The result is a site where a game page loads the engine and one game module and starts playing. There is no loading screen because there is nothing to load.

Common mistakes

  • Moving objects per frame rather than per secondEverything runs at double speed on a 120 Hz display. Multiply by elapsed time.
  • Not clamping delta timeA long stall advances the simulation in one huge step, moving fast objects through solid ones.
  • Writing to localStorage during gameplayThe API is synchronous and blocks the main thread. Write once at the end of a run.
  • Accessing localStorage without a try/catchIt throws in private browsing and with storage blocked, which crashes the game rather than degrading it.
  • Drawing instructions inside the canvasA canvas is opaque to assistive technology. Rules belong in real HTML outside it.

Practical tips

  • Games start instantly here because there are no assets to download - the art is code.
  • The tab pauses automatically when hidden, so switching away does not lose a run.
  • If a game page warns that storage is blocked, your scores will last only for that session.
  • Every game is keyboard playable because the input layer maps several keys to each action.
  • All game rules are real text on the page, which also makes them easier to look up than in most games.

Key takeaways

  • A browser arcade game is a canvas, a loop, an input layer and storage.
  • A canvas is stateless: you redraw everything every frame.
  • Clamp delta time, and express all movement per second rather than per frame.
  • Centralise input so accessibility decisions are made once.
  • Write storage rarely and always inside a try/catch.
  • No engine, framework, images or audio files are needed at this scale.

Frequently Asked Questions

What technology do HTML5 candy games use?

A canvas element for rendering, requestAnimationFrame for the loop, keyboard and pointer events for input, and localStorage for records. Ours add Web Audio for synthesised sound.

Do these games need a game engine?

No. At arcade-game scale an engine is typically more code than the games it supports. Our whole engine is about twenty small modules.

Why do the games start instantly?

Because there is nothing to download. All artwork is drawn from code and all audio is synthesised, so there are no assets to fetch or decode.

What is delta time and why does it matter?

The elapsed time since the last frame. Multiplying movement by it means a game runs at the same speed on a 60 Hz and a 120 Hz display.

Why are the game instructions written out on the page?

Because a canvas is opaque to screen readers. Putting the rules in real HTML outside it is the only way they can be reached by assistive technology.

References

  1. Canvas API - MDN Web Docs — The rendering interface used by every game here.
  2. requestAnimationFrame - MDN Web Docs — The loop primitive, including its automatic pausing behaviour.

Every external source listed here has been read. We do not cite sources we have not checked, and we do not invent references. See our fact-checking policy.