← All posts

Mastering 60fps Canvas Animation Loops for Browser Games

Learn how to build butter-smooth 60fps HTML5 canvas animation loops for browser games using requestAnimationFrame and delta-time physics.

If you have ever built a web game only to watch your pixelated hero stutter across the screen like a startled pigeon, you are already intimately acquainted with frame-rate drops. Building butter-smooth 60fps browser games sounds lovely in theory, but reality often involves sudden garbage collection pauses, heavy DOM reflows, and monitors refreshing at quirky rates like 144Hz or 90Hz.

With modern web games enjoying a massive resurgence across indie platforms and retro portals, getting your HTML5 canvas animation loop right is the difference between an addictive masterpiece and an unplayable motion-sickness simulator. Let us dive into the mechanics of building rock-solid game loops that keep your frame rates pristine.

Entity Definition: What is a Canvas Animation Loop?

Canvas Animation Loop Definition: A canvas animation loop is a continuous execution cycle in a web browser that repeatedly clears and redraws graphics onto an HTML5 <canvas> element. It synchronises game logic updates, physics calculations, and visual rendering to match the display's refresh rate—ideally targeting a steady 60 frames per second (fps).

The Cardinal Sin: Why setInterval Ruins Web Games

Back in the dark ages of web development, developers used setInterval or setTimeout to trigger frame updates. If you are still doing this, step away from the keyboard and take a deep breath.

Relying on timers for animation is a fool's errand. Browsers background tabs aggressively, timers drift due to the single-threaded nature of JavaScript event loops, and you end up burning CPU cycles rendering frames that the user cannot even see.

Instead, developers rely on requestAnimationFrame (rAF). This browser API tells the browser you wish to perform an animation and requests that the browser calls a specified function to update an animation right before the next repaint.


let lastTime = 0;

function gameLoop(timestamp) {
    if (!lastTime) lastTime = timestamp;
    const deltaTime = timestamp - lastTime;
    
    // Always update game state with delta time
    update(deltaTime);
    render();
    
    lastTime = timestamp;
    requestAnimationFrame(gameLoop);
}

// Kick off the loop
requestAnimationFrame(gameLoop);

The Secret Sauce: Delta Time Physics

Here is a common trap: you tie your game speed directly to your frame rate. You write player.x += 5 every time the loop ticks, assuming everyone on earth plays your game on a rigid 60Hz display.

Then a player joins with a high-end gaming laptop running a 240Hz monitor, and suddenly your casual puzzle platformer looks like a hyperactive squirrel on double espresso. Conversely, someone on a budget mobile phone experiences a slow-motion tragedy.

The fix? Delta time ($\Delta t$).

Delta time measures the exact millisecond duration between the previous frame and the current frame. By multiplying your movement speeds by this time differential, your game becomes entirely frame-rate independent.

ApproachSpeed ConsistencyHigh-Refresh Monitor BehaviourMobile Performance
Fixed Step (x += 5)TerribleRuns 4x too fast on 240HzChugs in slow motion
Delta Time MultiplierRock SolidButter-smooth at any HzAdjusts gracefully

Implementing Delta Time

Here is how you scale your velocity vectors using delta time in your update step:


function update(deltaTime) {
    // Convert deltaTime from milliseconds to seconds for saner math
    const deltaSeconds = deltaTime / 1000;
    
    // Player moves at a constant 300 pixels per second, regardless of frame drops
    player.x += player.vx * deltaSeconds;
    player.y += player.vy * deltaSeconds;
}

Community Insights: Fixing Jank and Micro-Stutters

If you lurk around the r/gamedev subreddits or check GitHub discussions on web performance, you will quickly notice a recurring villain: Garbage Collection (GC).

When your game loop instantiates new objects inside the render loop (like creating new vector objects or objects inside arrays), JavaScript engines eventually have to pause execution to sweep away the memory clutter. This causes those notorious micro-stutters that ruin an otherwise pristine 60fps experience.

Developer consensus across major web game post-mortems highlights three crucial optimisations:

1. Object Pooling: Pre-allocate your bullets, particles, and enemies in a static pool rather than spawning and destroying them on the fly.

2. Avoid DOM Thrashing: Never query layout properties (element.getBoundingClientRect()) inside your canvas update loop. Keep your UI elements separate from your canvas rendering pipeline.

3. Dirty Rectangles (When Applicable): For grid-based or turn-based browser games, only clear and redraw the specific canvas tiles that have actually changed, rather than wiping the entire viewport every single frame.

Key Takeaways

  • Abandon Timers: Never use setInterval for game loops; always use requestAnimationFrame to sync with the browser's repaint cycle.
  • Embrace Delta Time: Multiply your physics velocities by the time elapsed between frames to ensure consistent gameplay speeds across 60Hz, 120Hz, and variable mobile displays.
  • Watch Memory Allocation: Keep object creation out of your core loop to prevent garbage collection hiccups from tanking your frame rate.

Thanks for reading. Browse more from the Wobblox blog, or jump straight into all 100 free games.