Source Code

<div class="game-card">
  <div class="game-header">
    <div class="game-title">
      <span class="game-icon">🟪</span>
      <h2>Dodge the Blocks</h2>
    </div>
    <div class="best-badge">Best: <span id="best-score">0.0s</span></div>
  </div>

  <div class="canvas-wrap">
    <canvas id="game-canvas" width="360" height="480"></canvas>
    <div class="overlay" id="start-overlay">
      <div class="overlay-card">
        <p class="overlay-title">Dodge the Blocks</p>
        <p class="overlay-sub">Move with ← → or A/D. Survive as long as you can — it gets faster.</p>
        <button class="btn btn-primary" id="btn-start">Start</button>
      </div>
    </div>
    <div class="overlay hidden" id="gameover-overlay">
      <div class="overlay-card">
        <p class="overlay-title">Game Over</p>
        <p class="overlay-sub" id="final-score">Survived 0.0s</p>
        <p class="overlay-best" id="best-msg"></p>
        <button class="btn btn-primary" id="btn-retry">Try again</button>
      </div>
    </div>
    <div class="hud">
      <span id="hud-time">0.0s</span>
    </div>
  </div>

  <p class="hint-text">Arrow keys, A/D, or drag on touch devices to move left and right</p>
</div>

Dodge the Falling Blocks Game — Free HTML CSS JS Snippet

Dodge the Falling Blocks Game · Games · Plain HTML, CSS & JS · Live preview

What's included

Features

requestAnimationFrame game loop with delta-time (dt) scaling for frame-rate-independent movement
Real AABB (axis-aligned bounding box) collision detection between the player rectangle and every active falling block
Continuous difficulty ramp: fall speed and spawn frequency both increase as a function of elapsed survival time, not fixed steps
Unified keyboard (Arrow keys / A-D) and Pointer Events (mouse drag / touch drag) input driving the same player position
Canvas coordinate conversion via getBoundingClientRect() so touch/drag input works correctly at any rendered canvas size
ctx.roundRect() rounded-corner rendering with a rect() fallback for older browser engines
Persisted best survival time via localStorage with defensive try/catch guards
Clean start/game-over overlay states layered above the canvas without pausing the underlying render pipeline

About this UI Snippet

Dodge the Falling Blocks Game — Canvas Survival Game with AABB Collision and Ramping Difficulty

Screenshot of the Dodge the Falling Blocks Game snippet rendered live

Falling-object survival games are a compact, satisfying test of a canvas rendering loop because every core game-programming concept fits in a small surface area: continuous animation, real-time collision detection, and a difficulty curve that keeps the player engaged. This snippet builds a complete version using the HTML5 <canvas> API and requestAnimationFrame — a player rectangle confined to the bottom of the play field dodges randomly spawning blocks that fall from the top, with genuine axis-aligned bounding box (AABB) collision detection and a difficulty curve that ramps up the longer the run continues.

The game loop and frame-independent movement

The core loop is driven by requestAnimationFrame, calling loop(ts) on every repaint. Rather than moving objects by a fixed number of pixels per frame — which would make the game run faster or slower depending on the player's display refresh rate — the loop computes dt, the elapsed time in seconds since the previous frame, and clamps it to a maximum of 50ms to avoid physics glitches after a tab was backgrounded and resumed. Every movement calculation (block falling, difficulty ramp) is scaled by dt, making the game's speed consistent across 60Hz, 120Hz, or throttled displays — a standard technique for frame-independent motion.

Spawning and AABB collision detection

spawnBlock() creates a new falling block with a random width (between 28 and 84 pixels) and a random horizontal position clamped so the block never spawns partially off-canvas, added to a blocks array. Collision detection uses the classic axis-aligned bounding box test in rectsOverlap(): two rectangles overlap if and only if each one's horizontal range intersects the other's horizontal range *and* their vertical ranges also intersect — expressed as a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y. This is checked between the player's rectangle and every active block on every frame; any true result immediately ends the run. Blocks that fall past the bottom of the canvas without hitting the player are removed from the array to keep the collision check cheap regardless of how long the run lasts.

Genuine difficulty ramping

Both variables that control challenge are explicit, continuous functions of elapsed (seconds survived), not fixed constants or step changes. fallSpeed = 2.2 + elapsed * 0.09 means blocks fall measurably faster every second that passes. spawnInterval = Math.max(1100 - elapsed * 22, 320) means new blocks appear more frequently over time, shrinking the gap between spawns from 1.1 seconds down to a floor of 320 milliseconds so the game never becomes literally impossible to react to. Together these two curves mean the last thirty seconds of a long run are meaningfully denser and faster than the opening seconds — the difficulty is felt, not just theoretical.

Input: keyboard and touch in one code path

Movement responds to ArrowLeft/ArrowRight and A/D via keydown/keyup listeners that toggle a keys state object, read once per frame in update() — this avoids the classic bug of moving the player directly inside the event handler, which can feel jerky and framerate-dependent. For touch and mouse-drag input, the Pointer Events API (pointerdown/pointermove/pointerup) tracks a dragX value converted from screen coordinates to canvas coordinates via pointerToCanvasX(), which accounts for the canvas's actual rendered size versus its internal pixel resolution using getBoundingClientRect(). When dragX is set, it directly overrides keyboard movement for that frame, so the same player object works identically whether driven by keys or a finger.

Persisting the best survival time

getBest() and setBest() wrap localStorage access in try/catch for defensiveness, storing the longest survival time in seconds under dodge-blocks-best-time. endGame() compares the current run's elapsed value against the stored best and, if it's a new record, updates localStorage and displays a "New best time!" message alongside the always-visible best-score badge in the header.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to walk through exactly how the requestAnimationFrame loop, the dt-scaled fallSpeed/spawnInterval difficulty curve, and the rectsOverlap() AABB check work together each frame. It's a strong base to extend — ask the assistant to add a brief invincibility window after a near-miss, introduce power-up blocks (a different colour) that grant a temporary shield or slow-motion effect when collected instead of ending the run, add a particle-burst effect on collision using canvas, or refactor the single blocks array into an object pool to reduce garbage collection pressure during very long runs. You could also ask it to review the touch-drag pointer handling for edge cases on multi-touch devices, or to help port the canvas loop into a React component using useRef and useEffect for the animation frame lifecycle instead of top-level DOM code. Treat it as a working prototype to question and rebuild, not a finished black box.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a canvas-based falling-block dodge survival game in plain HTML, CSS, and JavaScript with ramping difficulty — no frameworks or libraries.

Requirements:
- A player rectangle confined horizontally within the bottom of a canvas, controllable via Arrow keys or A/D on keyboard, and via mouse drag or touch drag directly on the canvas (both input methods must move the same player state, not conflict).
- Blocks of random width spawn at random horizontal positions at the top of the canvas at a timed interval and fall downward at a constant-per-frame but continuously increasing speed.
- Use requestAnimationFrame for the game loop, and scale all per-frame movement by delta-time (elapsed seconds since the last frame) so gameplay speed stays consistent regardless of the display's actual frame rate.
- Implement real axis-aligned bounding box (AABB) collision detection between the player rectangle and every currently-falling block rectangle on every frame; colliding with any block must immediately end the run.
- Make the difficulty genuinely ramp over time: both the falling speed of blocks and the frequency at which new blocks spawn must increase continuously as a function of elapsed survival time, so the game is meaningfully harder in the later stages of a long run than at the very start (with a sensible minimum floor on spawn interval so it never becomes literally unplayable).
- Track elapsed survival time as the score, shown live during play, and show a Game Over screen with the final survival time when a collision occurs, plus a "Try again" button that fully resets the run.
- Persist the best (longest) survival time across page reloads using localStorage, guarded defensively in case storage access throws, and always display it in the UI.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Start the runClick "Start" on the opening overlay to call startGame(), which resets all state via resetState() and kicks off the requestAnimationFrame loop. The indigo player rectangle appears at the bottom-centre of the canvas.
  2. 2
    Move to avoid falling blocksUse ArrowLeft/ArrowRight or A/D to move the player horizontally — held keys are tracked in the keys object and applied every frame in update(). On touch devices, drag anywhere on the canvas; pointerToCanvasX() converts your finger position directly into the player's target X coordinate.
  3. 3
    Survive as long as possibleRed blocks of random width spawn at the top at an interval controlled by spawnInterval and fall at a speed controlled by fallSpeed — both increase continuously as elapsed time (shown in the top-right HUD) grows, via fallSpeed = 2.2 + elapsed * 0.09 and a shrinking spawnInterval floor of 320ms.
  4. 4
    Understand what ends the runEvery frame, rectsOverlap() checks the player's bounding box against every active block using real AABB (axis-aligned bounding box) intersection math. Any overlap immediately calls endGame(), stopping the animation loop and showing the Game Over overlay with your survival time.
  5. 5
    Beat your persisted best timeYour longest survival time is stored in localStorage under the key dodge-blocks-best-time and shown in the header badge at all times. Beating it on Game Over triggers a "New best time!" message and updates the stored value immediately.
  6. 6
    Retry or tune the difficulty curveClick "Try again" to call startGame() again with a freshly reset block array and difficulty curve. To make the game harder or easier overall, adjust the constants in update(): the 0.09 multiplier on fallSpeed and the 22 multiplier and 320 floor on spawnInterval.

Real-world uses

Common Use Cases

Reflex and reaction-time casual game for entertainment sites
Falling-object dodge games are a proven, low-friction casual game format — the entire ruleset is learnable in under five seconds of watching. Embed this on a games portal, blog sidebar, or app store landing page as an instantly playable demo that needs no tutorial, account, or download.
Mobile-friendly touch-drag interaction showcase
The Pointer Events-based drag control demonstrates a smooth, responsive touch interaction pattern suitable for any mobile web game or interactive product demo. Because pointerdown/pointermove/pointerup unify mouse and touch handling in one code path, this same input logic scales cleanly to a full mobile game without a separate touch implementation.
Loading screen or 404-page interactive distraction
A self-contained canvas game with no assets to load and no network dependency starts instantly, making it well suited to an empty-state screen, a slow-loading page, or an error page — similar in purpose to the Maze Runner Arrow-Key Game as an engaging alternative to a static spinner or illustration.
Teaching canvas rendering, overlay UI, and HUD design patterns
The layered structure — a full-bleed dark canvas, an absolutely positioned semi-transparent overlay for start/game-over states, and a small always-visible HUD in the corner — is a directly reusable pattern for any canvas-based interactive experience, from games to data visualisations that need a "loading" or "no data" overlay state.
Learn frame-independent motion and AABB collision from scratch
This snippet is a compact, readable reference for two foundational game-programming concepts: scaling movement by delta-time so gameplay speed is consistent regardless of frame rate, and axis-aligned bounding box collision detection, which underlies collision systems in countless 2D games before any physics engine is introduced.
Starting point for a richer arcade-style browser game
The core loop, spawn system, and difficulty curve generalise well — swap falling rectangles for sprite images, add power-ups that temporarily slow fallSpeed, introduce a shield or extra-life mechanic, or add a horizontal-scrolling parallax background. The AABB collision and dt-scaled update loop remain valid regardless of what visual complexity is layered on top.
Related: Color Match Reflex Game
See the Color Match Reflex Game for a related games pattern worth pairing with this one.
Related: Dot Muncher Maze Game
See the Dot Muncher Maze Game for a related games pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Browsers call requestAnimationFrame at a rate tied to the display's refresh rate — 60 times per second on most monitors, but up to 120 or more on high-refresh-rate displays, and potentially lower if the tab is throttled. Moving blocks by a fixed pixel amount per frame would make the game run visibly faster on a 120Hz display than a 60Hz one. Scaling every movement by dt (seconds elapsed since the last frame) keeps fall speed and spawn timing consistent in real time regardless of how many frames per second the browser is actually rendering.

Two axis-aligned rectangles overlap only if both their horizontal ranges intersect and their vertical ranges intersect simultaneously. rectsOverlap() checks a.x < b.x + b.width (rectangle a starts before b ends) and a.x + a.width > b.x (a ends after b starts) for the horizontal axis, then repeats the same pattern for y and height on the vertical axis. If all four conditions are true, the rectangles are guaranteed to overlap on both axes at once, meaning they visually intersect. This is one of the cheapest and most common collision tests in 2D game development because it only requires four comparisons.

Both difficulty curves live inside update(): fallSpeed = 2.2 + elapsed * 0.09 controls how quickly blocks fall, and spawnInterval = Math.max(1100 - elapsed * 22, 320) controls how often new blocks appear. Increase the 0.09 multiplier for a steeper speed ramp, increase the 22 multiplier for spawns to tighten up faster, or raise the 320 floor to keep a minimum breathing room between spawns even at very high elapsed times.

No — the best survival time is stored under the localStorage key dodge-blocks-best-time, which persists across page reloads and browser restarts on the same browser and origin. It only resets if the user clears site data, switches browsers or devices, or plays in a private/incognito window, since localStorage does not sync across those contexts without a backend.

Canvas is well suited to this kind of game because dozens of blocks can be moving and being collision-checked every frame; redrawing a bitmap surface with ctx.clearRect() and re-filling rectangles is typically cheaper than creating, animating, and destroying many individual DOM nodes with CSS transforms at 60fps. Canvas also makes it straightforward to add visual effects later (particle trails, screen shake, gradients) without touching the DOM tree at all.