Build a Browser Game in Pure HTML, CSS & JavaScript

By HTML Display Team8 min read

Before Steam, before app stores, before 100-gigabyte downloads, games were something a web page could just do. That never stopped being true; the industry simply moved on and forgot. Every browser you own ships with a complete game engine: a renderer, an input system, an audio stack, and a physics sandbox called JavaScript. In this guide you will build an HTML game from scratch, with no Unity license, no build pipeline and no approval process.

We’re building a classic: a paddle at the bottom of the screen, blocks falling from the top, catch them for points, miss three and it’s game over. It takes about 95 lines in a single file, the full HTML game code is at the end, and by then you’ll understand every line of it.

Every HTML Game Is Three Things

Strip away the graphics and every game from Pong to Elden Ring runs on the same skeleton: state (what exists right now), a loop (update everything, draw everything, repeat), and input (let the player interfere). Learn to see games this way and tutorials stop feeling like magic tricks.

HTML game architecture: state, game loop and player input

Our whole HTML game state fits in a handful of variables:

let paddleX = 170;
let blocks = [];
let score = 0;
let lives = 3;

That’s it. The paddle’s position, a list of falling blocks, and two numbers. Every frame, the loop will nudge this state forward and repaint the screen to match.

The Game Loop: Your New Heartbeat

Beginners reach for setInterval, but the modern web gave us something better: requestAnimationFrame, which syncs your updates to the display’s refresh rate for smooth motion and pauses automatically when the tab is hidden.

function gameLoop() {
  update(); // move blocks, check collisions
  draw();   // repaint the canvas
  if (lives > 0) requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);

Inside update(), gravity is just addition. Each block’s y position grows a little every frame, and difficulty is simply how fast:

function update() {
  for (const b of blocks) b.y += b.speed;
  // spawn a new block occasionally
  if (Math.random() < 0.02) {
    blocks.push({ x: Math.random() * 380, y: -20, speed: 1 + score / 10 });
  }
}

Notice the sneaky line: speed: 1 + score / 10. The better you play, the faster blocks fall. That’s a difficulty curve in eleven characters, and it’s the exact mechanism behind every “just one more run” arcade game since 1978. Game feel lives in tiny numbers like these.

HTML game loop moving falling blocks with requestAnimationFrame

Collision: The Moment Games Become Games

A game with no consequences is a screensaver. Collision detection is where consequences live, and for rectangles it’s four comparisons:

function caught(block) {
  return block.y > 440 &&
    block.x + 20 > paddleX &&
    block.x < paddleX + 60;
}

Read it aloud: the block is low enough, its right edge passed the paddle’s left edge, and its left edge hasn’t passed the paddle’s right edge. Every 2D game you’ve ever played, from Mario to Vampire Survivors, runs thousands of checks shaped exactly like this one every frame.

When a block is caught, bump the score and remove it; when one slips past the bottom, subtract a life. A single filter() call handles both:

blocks = blocks.filter((b) => {
  if (caught(b)) { score++; return false; }
  if (b.y > 480) { lives--; return false; }
  return true;
});

Input: Letting the Player In

Keyboard control is one event listener updating one variable:

document.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowLeft') paddleX = Math.max(0, paddleX - 25);
  if (e.key === 'ArrowRight') paddleX = Math.min(340, paddleX + 25);
});

The Math.max and Math.min clamps keep the paddle on screen, a two-function trick you’ll reuse in every game you ever write. Want mobile support? Add a touchmove listener that sets paddleX from the finger position, and your HTML game works on phones too. The browser’s input system already speaks every device; you just subscribe.

Drawing: Canvas Is Your GPU

The <canvas> element is a pixel playground with a drawing API that reads like instructions to a robot artist:

const ctx = document.querySelector('canvas').getContext('2d');

function draw() {
  ctx.fillStyle = '#0f172a';
  ctx.fillRect(0, 0, 400, 480);        // night-sky background
  ctx.fillStyle = '#3b82f6';
  ctx.fillRect(paddleX, 450, 60, 12);  // the paddle
  ctx.fillStyle = '#fbbf24';
  for (const b of blocks) ctx.fillRect(b.x, b.y, 20, 20); // falling blocks
  ctx.fillStyle = '#fff';
  ctx.fillText(`Score: ${score}   Lives: ${lives}`, 10, 24);
}

Clear, paddle, blocks, score. A frame later, everything has moved a few pixels, and persistence of vision does the rest. That illusion is all animation has ever been.

HTML game drawn on a canvas element with a paddle and falling blocks

The Complete HTML Game Code (Copy, Paste, Play)

Here is everything assembled into one HTML file, plus a game over screen and a restart key. Save it as index.html and open it in any browser: arrow keys to move, or drag with a finger on mobile, and press R to play again.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Block Catcher</title>
  <style>
    body { margin: 0; background: #020617; display: grid; place-items: center; height: 100vh; }
    canvas { background: #0f172a; border-radius: 8px; touch-action: none; }
  </style>
</head>
<body>
  <canvas width="400" height="480"></canvas>
  <script>
    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d');

    // State
    let paddleX = 170;
    let blocks = [];
    let score = 0;
    let lives = 3;

    // Input: keyboard and touch
    document.addEventListener('keydown', (e) => {
      if (e.key === 'ArrowLeft') paddleX = Math.max(0, paddleX - 25);
      if (e.key === 'ArrowRight') paddleX = Math.min(340, paddleX + 25);
    });
    canvas.addEventListener('touchmove', (e) => {
      const rect = canvas.getBoundingClientRect();
      const x = e.touches[0].clientX - rect.left;
      paddleX = Math.max(0, Math.min(340, x - 30));
      e.preventDefault();
    }, { passive: false });

    function caught(block) {
      return block.y > 440 &&
        block.x + 20 > paddleX &&
        block.x < paddleX + 60;
    }

    function update() {
      for (const b of blocks) b.y += b.speed;
      if (Math.random() < 0.02) {
        blocks.push({ x: Math.random() * 380, y: -20, speed: 1 + score / 10 });
      }
      blocks = blocks.filter((b) => {
        if (caught(b)) { score++; return false; }
        if (b.y > 480) { lives--; return false; }
        return true;
      });
    }

    function draw() {
      ctx.fillStyle = '#0f172a';
      ctx.fillRect(0, 0, 400, 480);
      ctx.fillStyle = '#3b82f6';
      ctx.fillRect(paddleX, 450, 60, 12);
      ctx.fillStyle = '#fbbf24';
      for (const b of blocks) ctx.fillRect(b.x, b.y, 20, 20);
      ctx.fillStyle = '#fff';
      ctx.font = '16px sans-serif';
      ctx.fillText(`Score: ${score}   Lives: ${lives}`, 10, 24);
    }

    function gameOver() {
      ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
      ctx.fillRect(0, 0, 400, 480);
      ctx.fillStyle = '#fff';
      ctx.font = 'bold 32px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('Game Over', 200, 220);
      ctx.font = '18px sans-serif';
      ctx.fillText(`Final score: ${score}`, 200, 255);
      ctx.fillText('Press R to play again', 200, 290);
      ctx.textAlign = 'left';
    }

    function gameLoop() {
      update();
      draw();
      if (lives > 0) requestAnimationFrame(gameLoop);
      else gameOver();
    }

    document.addEventListener('keydown', (e) => {
      if (e.key.toLowerCase() === 'r' && lives <= 0) {
        blocks = []; score = 0; lives = 3; paddleX = 170;
        requestAnimationFrame(gameLoop);
      }
    });

    requestAnimationFrame(gameLoop);
  </script>
</body>
</html>

That’s a complete, playable arcade game with a difficulty curve, in fewer lines than most webpack configs.

Test and Tweak Your Game Live

The best part of browser games is the iteration speed. Paste the HTML game code into a free online HTML editor with live preview and the edit-play loop collapses to zero: change the spawn rate, the preview restarts, you’re testing in the same second.

This tinkering phase is where programmers are actually made, one “what if I change this number” at a time. Try doubling the paddle speed, shrinking the paddle, or raising the spawn chance from 0.02 to 0.04 and feel how much the game changes.

5 Easy Upgrades for Your HTML Game

Once your HTML game loop works, each of these takes a few minutes and teaches one new browser API:

  • High score: save the best score with localStorage.setItem('best', score) so it survives a page reload.
  • Bonus blocks: give one block in ten a red color and make it worth double points.
  • Sound effects: play a short beep on each catch with the Web Audio API or a tiny new Audio('catch.mp3').
  • Pause key: stop calling requestAnimationFrame while a paused flag is true, and toggle it with the P key.
  • Smooth movement: track which arrow keys are held down and move the paddle a few pixels every frame instead of jumping 25 pixels per key press.

Each upgrade touches only one part of the skeleton (state, loop or input), which is exactly why that structure is worth learning first.

Put Your Game Online

When your HTML game deserves an audience, remember it’s just an HTML file. Put it online and send a link: no store, no installer, no gatekeeper, which is more than most indie platforms can say even now.

HTML Game FAQ

Can you make a game with just HTML?

HTML gives you the page and the <canvas> element, CSS styles it, and JavaScript runs the game logic. You need all three, but nothing else: no engine, library or install.

Do I need a game engine or framework?

Not for small 2D games like this one. Libraries such as Phaser help once you need sprites, physics or many levels, but writing your first HTML game by hand teaches you what those tools do for you.

Will my HTML game work on phones?

Yes. Canvas works in every mobile browser, and the touchmove listener in the full code lets players drag the paddle with a finger.

How do I share my game with friends?

Upload the single HTML file to any static host and share the URL. Anyone with a browser can play instantly.

The web was the world’s first open game console, and it still is. What HTML game are you going to build first: catcher, snake, or something nobody’s seen yet?

Try the code from this guide

Paste it into the free editor and see it render live, no signup needed.

Open the HTML editor