When people hear that the games on GGAMES.MOBI are built with plain HTML, CSS, and JavaScript—no Phaser, no Unity WebGL, no Pixi.js—the first reaction is usually surprise. Game frameworks exist for a reason, and they are genuinely excellent tools. But for the kinds of games we build—classic puzzle and board games like 2048, Minesweeper, Sudoku, Snake, Checkers, Connect Four, and Memory Match—going frameworkless is a deliberate choice with real benefits.
The Case for Vanilla JavaScript
The strongest argument for building browser games without a framework comes down to what you actually need versus what a framework provides. Consider what Phaser brings to the table: a scene graph, physics engines (Arcade, Matter.js), sprite sheets, animation systems, particle emitters, tilemap support, audio management, and a plugin ecosystem. That is an enormous amount of capability, and the minified build weighs around 1 MB.
Now consider what a game like 2048 needs: a grid, some colored tiles, keyboard input, touch swipe detection, a score counter, and smooth CSS transitions. Everything that game needs is already in the browser. Adding a framework is like hiring a construction crew to hang a picture frame.
The practical benefits of going vanilla:
- File size: Our Minesweeper implementation is a single HTML file under 30 KB. Users on slow connections or older devices load the game almost instantly.
- Load time: Zero framework initialization. The game is interactive as soon as the HTML parses. There is no asset pipeline, no loader screen, no "please wait while we initialize the engine."
- No build step: Edit the file, refresh the browser, see the change. The feedback loop is measured in milliseconds. This is especially valuable when iterating on game feel and tuning.
- No dependencies to break: There is no
package.json, nonode_modules, no version conflicts. The game works today and will work in ten years because it depends only on stable web standards. - Learning fundamentals: Building a game loop from scratch teaches you what
requestAnimationFrameactually does. Handling collision detection yourself means you understand the math, not just the API.
When Frameworks Genuinely Help
This is not a religious argument. Frameworks earn their weight in specific scenarios, and pretending otherwise would be dishonest:
- Complex physics: If your game needs realistic rigid body physics, friction, joints, and constraints, writing your own physics engine is a poor use of time. Use Matter.js or Box2D.
- 3D rendering: WebGL programming without Three.js or Babylon.js is extraordinarily tedious. The boilerplate for shaders, buffers, and matrices alone would dwarf your game logic.
- Multiplayer with prediction: Networked games with client-side prediction and server reconciliation benefit enormously from frameworks that have solved these timing problems already.
- Sprite-heavy games: If you are building a platformer with hundreds of animated sprites, tilemaps, and parallax scrolling, a framework's rendering pipeline will outperform naive Canvas draw calls significantly.
- Large team projects: When multiple developers need to work on the same game, a framework's conventions and structure reduce coordination overhead.
The decision is about matching the tool to the task. A tile-matching puzzle game has different requirements than a real-time multiplayer shooter.
The Canvas API Is More Capable Than You Think
The HTML5 Canvas API provides everything you need for 2D game rendering. It is well-documented, universally supported, and performant for the kinds of games that work well in a browser. Here is a minimal but complete rendering setup:
var canvas = document.getElementById("game");
var ctx = canvas.getContext("2d");
function drawBoard(state) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid
ctx.strokeStyle = "#ccc";
for (var i = 0; i <= state.cols; i++) {
ctx.beginPath();
ctx.moveTo(i * state.cellSize, 0);
ctx.lineTo(i * state.cellSize, canvas.height);
ctx.stroke();
}
// Draw game pieces
for (var r = 0; r < state.rows; r++) {
for (var c = 0; c < state.cols; c++) {
if (state.grid[r][c]) {
drawPiece(ctx, r, c, state.grid[r][c], state.cellSize);
}
}
}
}
For games that do not need per-pixel rendering—board games, card games, puzzle games—you can skip Canvas entirely and use DOM elements with CSS transitions. Our 2048 clone uses this approach. Each tile is a <div> positioned with CSS transforms, and the sliding animation is a CSS transition. The browser's compositor handles the animation on the GPU, resulting in silky smooth 60fps movement with zero JavaScript animation code.
The Game Loop Pattern
Every real-time game needs a loop that updates state and renders frames. The browser provides requestAnimationFrame for exactly this purpose. A basic game loop with fixed timestep looks like this:
var TICK_RATE = 1000 / 60; // 60 updates per second
var lastTime = 0;
var accumulator = 0;
function gameLoop(timestamp) {
var deltaTime = timestamp - lastTime;
lastTime = timestamp;
accumulator += deltaTime;
// Fixed timestep updates
while (accumulator >= TICK_RATE) {
updateGameState(TICK_RATE);
accumulator -= TICK_RATE;
}
render();
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
The fixed timestep pattern separates game logic from rendering. The game state updates at a consistent rate regardless of the display refresh rate. This prevents the common bug where game speed varies on different monitors—a 144Hz monitor should not make your Snake game move faster than a 60Hz one.
For turn-based games like Checkers, Sudoku, or Minesweeper, you do not need a continuous game loop at all. These games are event-driven: update state on click, re-render, wait for the next click. This makes them even simpler to implement without a framework.
Handling Touch Events for Mobile
Mobile support is non-negotiable for browser games. On GGAMES.MOBI, a significant portion of traffic comes from phones and tablets. Touch handling requires attention to a few details that frameworks normally abstract away:
- Swipe detection: Track
touchstartandtouchendpositions, calculate the delta, and determine the dominant direction. A threshold of 30 pixels works well to distinguish deliberate swipes from accidental touches. - Prevent scroll: Call
e.preventDefault()on touch events within the game area to prevent the page from scrolling when the player swipes. - Touch coordinates: Use
e.touches[0].clientXfor position, note.pageX. Remember to subtract the canvas offset to get coordinates relative to the game area. - Click delay: Modern browsers have largely eliminated the 300ms tap delay, but using
touch-action: manipulationin CSS ensures it.
var touchStartX, touchStartY;
canvas.addEventListener("touchstart", function(e) {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
});
canvas.addEventListener("touchend", function(e) {
e.preventDefault();
var dx = e.changedTouches[0].clientX - touchStartX;
var dy = e.changedTouches[0].clientY - touchStartY;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 30) {
handleSwipe(dx > 0 ? "right" : "left");
} else if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 30) {
handleSwipe(dy > 0 ? "down" : "up");
}
});
Responsive Canvas Sizing
A game that only works at one resolution is a game that frustrates half your players. Responsive canvas sizing involves two concerns: the CSS display size and the internal resolution.
function resizeCanvas() {
var container = document.getElementById("game-container");
var size = Math.min(container.clientWidth, 500);
canvas.style.width = size + "px";
canvas.style.height = size + "px";
canvas.width = size * window.devicePixelRatio;
canvas.height = size * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
render(); // Re-draw at new size
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
Multiplying by devicePixelRatio ensures sharp rendering on high-DPI screens (Retina displays, modern phones). Without this, your game looks blurry on devices where one CSS pixel maps to two or more physical pixels.
Lessons from Building Games on GGAMES.MOBI
After building multiple games with this approach, a few patterns have emerged:
- State and rendering should be completely separate. The game state is a plain JavaScript object. The render function reads from that object and draws. This makes it trivial to add features like undo (save previous state) or AI opponents (clone the state, run simulations).
- CSS does the heavy lifting for board games. Grid layouts, transitions, and transforms handle 90% of the visual work for games like 2048 and Memory Match. Use Canvas only when you need per-pixel control.
- Single-file deployment is a superpower. Each game is one HTML file with inline CSS and JavaScript. Deploy it anywhere—a CDN, a shared host, even email it to someone. No build, no dependencies, no CORS issues.
- Test on real devices early. Touch handling, viewport sizing, and performance on low-end Android devices always reveal surprises that desktop development misses.
Building browser games without frameworks is not about rejecting modern tools. It is about recognizing that the browser itself is a remarkably capable game engine for the right class of games. When your game fits in that class—2D, moderate complexity, turn-based or simple real-time—the vanilla approach gives you faster loads, simpler code, and total control. And honestly, it is more fun to build things from scratch.