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:

When Frameworks Genuinely Help

This is not a religious argument. Frameworks earn their weight in specific scenarios, and pretending otherwise would be dishonest:

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:

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:

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.