How to Create Your Own Simple Browser Game in JavaScript (Step-by-Step Guide)
Building your first browser game is one of the most satisfying things you can do as a developer. You write a few dozen lines of vanilla JavaScript, hit refresh, and suddenly something moves on screen because of code you wrote. No downloads, no installs — just a browser and a text editor.
This guide walks you through building a complete, playable dodge game from scratch. By the end, you'll have a player that moves, obstacles that scroll toward it, a live score counter, and a game-over state. Every major concept gets a code snippet you can copy and run immediately.
What You Need Before You Start
You need basic familiarity with HTML, CSS, and JavaScript — specifically variables, functions, and event listeners. That's the honest minimum. You don't need a framework, a game engine, or any paid tools.
For tooling, all you need is:
- A text editor (VS Code is free and excellent)
- A modern browser (Chrome or Firefox both work perfectly)
- No terminal, no npm, no build step
If you've built a simple to-do list or a form validator in JavaScript, you have enough background to follow this tutorial. The concepts introduced here — the game loop, collision detection, and canvas rendering — will feel unfamiliar at first, but each one is explained before you use it.
Setting Up Your Project Files
Create a folder called my-game and add three files inside it: index.html, style.css, and game.js. This is the entire project structure you need.
Here's your starting index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Browser Game</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="gameCanvas" width="600" height="400"></canvas>
<script src="game.js"></script>
</body>
</html>
In style.css, center the canvas and give the page a dark background:
body {
margin: 0;
background: #1a1a2e;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
canvas { background: #16213e; display: block; }
Open index.html in your browser. You should see a dark rectangle — that's your blank canvas, ready to go.
Drawing on the HTML5 Canvas
The HTML5 Canvas is a drawing surface you control entirely through JavaScript. You get a reference to it, grab its 2D rendering context, and then call drawing methods to put shapes on screen.
Add this to game.js:
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Draw the player
ctx.fillStyle = '#e94560';
ctx.fillRect(50, 175, 40, 40);
Reload the page. A red square appears. That square is your player. fillRect(x, y, width, height) is the workhorse method you'll use throughout this project — it draws a filled rectangle at the given coordinates.
The coordinate system starts at the top-left corner: x increases rightward, y increases downward. Keep that in mind when you position things.
Building the Game Loop with requestAnimationFrame
requestAnimationFrame is the correct way to run a game loop in a browser. It tells the browser to call your update function before the next screen repaint — typically 60 times per second — which keeps animations smooth and CPU-friendly.
Replace the static drawing code in game.js with this loop:
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let player = { x: 50, y: 175, width: 40, height: 40 };
function update() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw player
ctx.fillStyle = '#e94560';
ctx.fillRect(player.x, player.y, player.width, player.height);
requestAnimationFrame(update);
}
update();
The critical step here is ctx.clearRect() at the start of every frame. Without it, every new frame draws on top of the previous one and you get a smeared mess. Clear first, then redraw everything from scratch — that's the fundamental rhythm of browser rendering.
Adding Player Movement with Keyboard Input
Attach keydown and keyup event listeners to track which keys are currently pressed, then move the player object each frame based on that state.
Add this before your update function:
const keys = {};
document.addEventListener('keydown', e => keys[e.key] = true);
document.addEventListener('keyup', e => keys[e.key] = false);
const speed = 4;
Then inside update(), before drawing the player, add movement logic:
if (keys['ArrowUp'] && player.y > 0) player.y -= speed;
if (keys['ArrowDown'] && player.y + player.height < canvas.height) player.y += speed;
if (keys['ArrowLeft'] && player.x > 0) player.x -= speed;
if (keys['ArrowRight'] && player.x + player.width < canvas.width) player.x += speed;
The boundary checks (player.y > 0, etc.) keep the player object from sliding off the canvas edges. Try removing them temporarily to see what happens — it's a useful way to understand why they're there.
For mobile players, you can add touch controls later by mapping touchstart events to the same keys object, or by overlaying directional buttons on the canvas.
Adding a Simple Obstacle and Collision Detection
A moving obstacle and bounding-box collision detection are what turn a drawing demo into an actual game. The obstacle scrolls from right to left; when it overlaps the player rectangle, the game ends.
Add an obstacle object and a game state variable:
let obstacle = { x: 620, y: 160, width: 30, height: 80, speed: 3 };
let gameOver = false;
let score = 0;
Inside update(), after moving the player, add:
if (!gameOver) {
// Move obstacle
obstacle.x -= obstacle.speed;
if (obstacle.x + obstacle.width < 0) {
obstacle.x = canvas.width;
obstacle.y = Math.random() * (canvas.height - obstacle.height);
score++;
}
// Draw obstacle
ctx.fillStyle = '#f5a623';
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
// Collision detection (bounding box)
if (
player.x < obstacle.x + obstacle.width &&
player.x + player.width > obstacle.x &&
player.y < obstacle.y + obstacle.height &&
player.y + player.height > obstacle.y
) {
gameOver = true;
}
}
Bounding-box collision detection checks whether two rectangles overlap by testing all four axis conditions simultaneously. It's not pixel-perfect, but for a casual browser game it's fast, readable, and completely sufficient. You can find a more detailed explanation of the math behind it on MDN's 2D collision detection guide.
Displaying the Score and Polishing the Game
Render the score directly onto the canvas using ctx.fillText(), and show a restart prompt when the game ends. These two additions make the game feel complete.
Inside update(), after all game logic, add:
// Score display
ctx.fillStyle = '#ffffff';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + score, 10, 25);
// Game over screen
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.font = '40px Arial';
ctx.fillText('Game Over', 200, 180);
ctx.font = '20px Arial';
ctx.fillText('Press R to restart', 220, 220);
}
Then add a restart listener:
document.addEventListener('keydown', e => {
if (e.key === 'r' && gameOver) {
player.x = 50; player.y = 175;
obstacle.x = 620;
score = 0;
gameOver = false;
}
});
A few quick tweaks that improve the feel significantly: increase obstacle.speed by 0.5 each time the player survives a pass (progressive difficulty), randomize the obstacle's height between 40 and 100 pixels, and add a subtle color shift to the player as speed increases. None of these require new concepts — they're just adjustments to values you already control.
You now have a complete, playable casual browser game built with nothing but vanilla JavaScript and an HTML file. The full game runs in any modern browser, requires zero dependencies, and can be hosted for free on platforms like GitHub Pages or Netlify by simply uploading the folder.
Frequently Asked Questions
Do I need a framework like Phaser to make a browser game?
No. Phaser and similar frameworks are useful for larger projects, but they add significant complexity for beginners. Vanilla JavaScript with HTML5 Canvas handles everything a simple browser game needs. Learn the fundamentals first — frameworks make much more sense once you understand what they're abstracting.
Can I add images or sprites instead of plain shapes?
Yes. Use ctx.drawImage() with an Image object to render PNG or sprite sheet assets onto the canvas. The positioning logic stays identical — you're just swapping fillRect() for drawImage(). Start with colored rectangles to get the logic right, then swap in graphics.
How do I make the game run at the same speed on all devices?
Use delta time. requestAnimationFrame passes a timestamp to your callback; calculate the difference between frames and multiply movement values by that delta. This decouples game speed from frame rate, so the game runs consistently whether the device manages 30fps or 60fps.
Can I host my finished browser game for free?
Easily. GitHub Pages, Netlify, and Vercel all offer free static hosting. Push your three files to a GitHub repository, enable Pages in the repo settings, and your game is live at a public URL within minutes.
What is a good next project after finishing this tutorial?
Try a Breakout/Arkanoid clone — it introduces ball physics, paddle collision with angle variation, and a brick grid. It reuses every concept from this guide while adding just enough new complexity to keep things interesting. The MDN Breakout tutorial is an excellent follow-up resource.