Why Build Browser Games?
Browser games are the most accessible games on the planet. No download, no installation, no app store account — just open a link and play. If you've ever wanted to make your own game, starting with browser game development is the smartest move. The tools are free, the audience is huge, and your game can run on any device with a browser.
In this tutorial, you'll build a complete Snake game using HTML5 Canvas and JavaScript. By the end, you'll understand the core concepts behind every browser game: the game loop, rendering, input handling, collision detection, and scoring. These same principles apply to any game you want to build next.
Step 1: Set Up Your Project
You don't need any special software to start. Here's all you'll need:
- A text editor — we recommend VS Code (free, works on all platforms)
- A modern web browser — Chrome, Firefox, Safari, or Edge
- A folder on your computer to store your project files
Create a new folder called "my-game" and open it in VS Code. Then create a new file called index.html. That's where all your game code will live.
Step 2: Create the HTML Foundation
Every HTML5 game starts with a canvas — a rectangular drawing area where your game renders. Add this basic HTML structure to your index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Snake Game</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #0a1628;
margin: 0;
}
canvas {
border: 2px solid #00ff88;
border-radius: 4px;
}
</style>
</head>
<body>
<canvas id="gameCanvas" width="400" height="400"></canvas>
<script>
// Your game code goes here
</script>
</body>
</html>
Open this file in your browser and you should see a dark background with a green-bordered canvas. That's your game's canvas — the area where all the action happens.
Step 3: Initialize the Game Canvas
Now let's write the JavaScript that makes the game actually work. Inside the <script> tags, add this initialization code:
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const gridSize = 20;
const tileCount = canvas.width / gridSize;
let snake = [
{ x: 10, y: 10 }
];
let food = { x: 15, y: 15 };
let dx = 0;
let dy = 0;
let score = 0;
function drawGame() {
// Clear the canvas
ctx.fillStyle = '#0a1628';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the snake
ctx.fillStyle = '#00ff88';
snake.forEach(part => {
ctx.fillRect(part.x * gridSize, part.y * gridSize, gridSize - 2, gridSize - 2);
});
// Draw the food
ctx.fillStyle = '#ff6b6b';
ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize - 2, gridSize - 2);
}
Step 4: Implement the Game Loop
The game loop is the heartbeat of every game. It runs over and over, updating the game state and redrawing the screen. For Snake, we want the game to update at a steady pace — about 10 times per second (every 100 milliseconds). Add this code after your initialization:
function update() {
// Move the snake head
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
snake.unshift(head);
// Check if snake ate the food
if (head.x === food.x && head.y === food.y) {
score += 10;
placeFood();
} else {
snake.pop(); // Remove the tail if no food eaten
}
}
function placeFood() {
food = {
x: Math.floor(Math.random() * tileCount),
y: Math.floor(Math.random() * tileCount)
};
}
function gameLoop() {
update();
drawGame();
}
setInterval(gameLoop, 100);
At this point, you have a snake that moves on its own and eats food. But you still can't control it — let's add keyboard input.
Step 5: Handle Player Input
Add an event listener to capture arrow key presses. The snake should not be able to reverse direction directly (you can't go left if you're moving right):
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowUp':
if (dy === 0) { dx = 0; dy = -1; }
break;
case 'ArrowDown':
if (dy === 0) { dx = 0; dy = 1; }
break;
case 'ArrowLeft':
if (dx === 0) { dx = -1; dy = 0; }
break;
case 'ArrowRight':
if (dx === 0) { dx = 1; dy = 0; }
break;
}
});
Step 6: Add Collision Detection
No game is complete without a way to lose. The snake should die if it hits the wall or its own body. Update your update() function:
function update() {
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
// Wall collision
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
gameOver();
return;
}
// Self collision
for (let i = 0; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
gameOver();
return;
}
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
placeFood();
} else {
snake.pop();
}
}
function gameOver() {
alert('Game Over! Score: ' + score);
snake = [{ x: 10, y: 10 }];
dx = 0;
dy = 0;
score = 0;
}
function placeFood() {
food = {
x: Math.floor(Math.random() * tileCount),
y: Math.floor(Math.random() * tileCount)
};
}
function update() {
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
// Wall collision
if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
gameOver();
return;
}
// Self collision
for (let i = 0; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
gameOver();
return;
}
}
snake.unshift(head);
if (head.x === food.x && head.y === food.y) {
score += 10;
placeFood();
} else {
snake.pop();
}
}
function drawGame() {
ctx.fillStyle = '#0a1628';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#00ff88';
snake.forEach(part => {
ctx.fillRect(part.x * gridSize, part.y * gridSize, gridSize - 2, gridSize - 2);
});
ctx.fillStyle = '#ff6b6b';
ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize - 2, gridSize - 2);
ctx.fillStyle = '#fff';
ctx.font = '20px Arial';
ctx.fillText('Score: ' + score, 10, 25);
}
function gameLoop() {
update();
drawGame();
}
setInterval(gameLoop, 100);
// Initialize
placeFood();
drawGame();