If you have ever tried coding a browser-based bullet hell or an indie particle sandbox in HTML5 Canvas, you already know the sinking feeling. You load up your game, spawn a few hundred colourful circles, and suddenly your frame rate drops lower than a sluggish smartphone battery in midwinter.
What went wrong? You built a naive collision system.
Every single frame, your physics loop checks every object against every other object. That is the dreaded $O(N^2)$ time complexity. If you have 500 objects, your poor CPU has to calculate 250,000 checks per frame. At 60 frames per second, you are asking your browser tab to melt itself.
Thankfully, developers across the web have been buzzing about a clean, elegant fix borrowed from large-scale game engines: Spatial Hash Grids. Whether you are building the next viral indie browser hit or just messing about with canvas physics on a rainy afternoon, implementing a spatial hash grid in vanilla JavaScript is your ticket to silky-smooth 60 FPS gameplay.
What is a Spatial Hash Grid? (Entity Definition)
Spatial Hash Grid Definition: A spatial partition data structure that divides a 2D game world into a grid of discrete cells. Instead of checking every object against the entire map, game entities are registered only to the specific grid cells they currently occupy, reducing broad-phase collision checks from $O(N^2)$ down to near $O(N)$.
Think of it like a spreadsheet laid across your HTML5 Canvas. When an entity moves, it reports its coordinates to the grid. When it is time to check for collisions, the game engine only checks objects sharing the exact same cell or its immediate neighbours. It is like only checking if the person sitting directly next to you on the train has pinched your lunch, rather than frisking all 500 passengers.
Why the Community Loves Spatial Hashing
If you lurk around game development subreddits, GitHub discussions, or watch technical browser-game breakdowns on YouTube, spatial hashing is frequently crowned the king of casual 2D physics.
- Quadtrees: Brilliant for static terrain, but notoriously finicky to rebuild every single frame when dealing with hundreds of fast-moving, bouncy entities.
- Spatial Hash Grids: Incredibly lightweight, easy to implement in plain JavaScript, and lightning-fast to clear and repopulate every tick.
Community experiments show that for dynamic particle systems and arcade-style browser games, spatial grids offer the best trade-off between memory overhead and query speed.
Implementation: Building the Grid in Vanilla JavaScript
Let us dive straight into the code. Below is a clean, modern ES6 implementation of a Spatial Hash Grid tailored for HTML5 Canvas games.
class SpatialHashGrid {
constructor(cellSize) {
this.cellSize = cellSize;
this.cells = new Map();
}
// Convert world coordinates to a unique grid key
hash(x, y) {
const cellX = Math.floor(x / this.cellSize);
const cellY = Math.floor(y / this.cellSize);
return `${cellX},${cellY}`;
}
// Clear the grid at the start of every frame
clear() {
this.cells.clear();
}
// Insert an entity based on its bounding box or position
insert(entity) {
const key = this.hash(entity.x, entity.y);
if (!this.cells.has(key)) {
this.cells.set(key, []);
}
this.cells.get(key).push(entity);
}
// Retrieve potential collision candidates near an entity
getPotentialCollisions(entity) {
const candidates = [];
const radius = entity.radius || 0;
// Check surrounding cells to handle boundary overlaps
for (let dx = -radius; dx <= radius; dx += this.cellSize) {
for (let dy = -radius; dy <= radius; dy += this.cellSize) {
const key = this.hash(entity.x + dx, entity.y + dy);
if (this.cells.has(key)) {
candidates.push(...this.cells.get(key));
}
}
}
return candidates;
}
}
Comparing Broad-Phase Approaches
To understand why this trick saves your frame rate, let us look at how different collision strategies stack up against each other in browser environments.
| Strategy | Time Complexity | Setup Overhead | Best Used For |
|---|---|---|---|
| Brute Force | $O(N^2)$ | Zero | Tiny demos (< 50 objects) |
| Quadtree | $O(N \log N)$ | Moderate to High | Static worlds, UI layouts |
| Spatial Hash Grid | $O(N)$ (Average) | Low | Dynamic particles, bullets, arcade physics |
High-Score Tips for Canvas Performance
If you want to keep your browser game running buttery smooth, keep these quick optimisation rules in mind:
1. Cell Size Tuning: Set your cellSize to roughly match the average diameter of your game entities. If cells are too small, objects span multiple cells and slow things down. If they are too large, you end up checking too many objects per cell.
2. Reuse Data Structures: Garbage collection pauses are the silent killer of 60 FPS JavaScript games. Avoid instantiating new arrays inside your collision loops; reuse pre-allocated arrays where possible.
3. Broad-Phase vs. Narrow-Phase: Remember that spatial hashing is only the broad phase. It gives you a short list of potential collisions. You still need a cheap narrow-phase check (like standard circle distance formulas) to confirm actual impacts.