HTML game code

HTML game code runs anywhere a browser does, as long as everything it needs is inside the file. Sharing it is the part that usually fails.

HTML game code runs in any browser without a build step, provided everything it needs lives inside the one file. Put that file at an address and the game is playable in a click.

A canvas game running in a browser tab, score drawn on the canvas, no external files loaded.
A canvas game running in a browser tab, score drawn on the canvas, no external files loaded.

Most trouble with game HTML is not the game logic. It is that the page reaches for a sprite sheet or a script file that only exists in your folder.

What HTML game code needs to stand alone

The shape is always the same. One document, three regions.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Falling blocks</title>
  <style> canvas { background:#111; display:block; margin:0 auto } </style>
</head>
<body>
  <canvas id="board" width="320" height="480"></canvas>
  <script>
    const ctx = document.getElementById('board').getContext('2d');
    // game loop here
  </script>
</body>
</html>

No src pointing at a neighbouring file, no link rel="stylesheet", no module import. That is the whole requirement for the page to travel.

What breaks when the file moves

In the code Runs from a double click Runs when shared Fix
Inline <script> Yes Yes Nothing
<script src="game.js"> Yes, if the file is beside it No Paste the script inline
<script type="module"> Blocked under file protocol Yes, when served Serve the page, or drop the module syntax
new Image().src = 'sprite.png' Yes, if the file is beside it No Embed as a data URI
fetch('levels.json') Blocked under file protocol Yes, when served Inline the data as a JS object
localStorage Yes Yes, per address Nothing

The blocked rows are not bugs in your code. Browsers restrict what a page opened from disk is allowed to read, which is what the file protocol does.

Embedding the assets

Two routes, and which you pick depends on the size of the art.

Draw it. For anything geometric, canvas drawing commands are smaller than an image and never fail to load. Most puzzle and arcade prototypes need no images at all.

Embed it. For real artwork, convert the image to a base64 data URI and put it in the file. The file gets larger, and it becomes one thing that works everywhere.

Sound is the awkward case. Short effects embed fine as data URIs. Music does not, so either hotlink it to a full address or generate tones with the audio API.

For the general pattern, see self-contained HTML files.

Getting the game in front of players

Game HTML pasted into a NOS document. The script runs and the canvas is playable in the rendered page.
Game HTML pasted into a NOS document. The script runs and the canvas is playable in the rendered page.
  1. Test it somewhere neutral. Open the file in the HTML file opener. It has never seen your folder, so anything missing shows up immediately.
  2. Paste the complete HTML into a document. It renders as a page of its own with scripts running.
  3. Create the share link. Share, then Share link, then Create link. Unlisted by default.
  4. Send the line. It opens in a tap on a phone and needs no install.
The share panel with the link created, ready to paste into a chat.
The share panel with the link created, ready to paste into a chat.

Sending the .html file instead is the common mistake. Mail gateways strip HTML attachments, and on a phone the file lands in storage with nothing offering to run it, which is why links beat attachments.

Input and saved progress on phones

Keyboard controls are the default in generated game code and they exclude most of your audience. Two additions cover it.

Add touch handlers alongside the key handlers, mapping taps on screen regions to the same actions. Then add the viewport meta tag, without which the canvas renders at desktop scale and the game is unplayably small.

Keep the canvas sized in CSS pixels and scale the drawing context, rather than fixing pixel dimensions that only suit your monitor.

A single file game can keep state, within limits. Local storage holds a high score or a save slot, tied to one browser at one address.

That means the score does not follow the player to another device, and it is not visible to anyone else. A shared leaderboard needs a server, which is outside what a single file can do.

Session storage is the shorter lived alternative, cleared when the tab closes, which suits a run in progress.

When the code came from an AI chat

Generated game HTML is usually already one file, which is the format you want. Three checks.

Copy the complete output, from the doctype to the closing tag. Long game loops get truncated in chat panes and a half copied script fails silently.

Open it once on its own before sharing. If the canvas stays blank, the usual cause is an asset path the chat invented, or a module import that the file protocol blocks.

Then paste and share. Editing the title or the instructions afterwards is possible without touching the code. For the wider pattern, see single HTML file games.

Debugging and performance

Open the console before touching the code. A game that draws nothing has usually thrown an error in the first frame.

Reference errors name the missing thing directly, usually a function defined after it is called, or an element queried before the document exists. Move the script to the end of the body.

Blocked requests mean the page tried to load something. Under the file protocol that is fatal, and the console names the address it wanted.

A blank canvas with no errors usually means the drawing context is fine and the loop never started. Check that the next animation frame is requested at the end of each frame.

Phones run these pages, and a loop written on a desktop can drop frames badly.

Keep drawing calls per frame low and avoid creating objects inside the loop, because garbage collection shows up as periodic stutter.

Clear only the region that changed rather than the whole canvas where you can. And stop the loop when the tab is hidden, which spares the battery of anyone who left your game open.

Questions people ask

Can I run HTML game code without a server?

Often yes. A single file with inline script and no external assets runs from a double click. It stops working the moment the code fetches a file, loads a module, or reads an image, because browsers block those under the file protocol.

Why does my HTML game work locally but not for other people?

Almost always because it depends on files sitting next to it on your machine. Sprites, sound, a separate script file. Send only the HTML and those references point at nothing. Embed them, or put the whole page at an address.

How do I share a playable HTML game?

Paste the complete HTML into a document that renders it, create a share link, and send that. Scripts run, so the game is playable in one click on any device with a browser.

Do saved scores survive?

If the game writes to local storage, the score is kept per browser and per address. It is not shared between players and it does not follow a player to another device. For a leaderboard you need a backend.

Keep reading