Building 60 FPS Physics Engines in HTML5 Canvas: Delta Time, Verlet Integration & Jank-Free Rendering

Creating physical simulations in the browser—whether an interactive particle field, ragdoll mechanics, or gravitational orbital bodies—is one of the most rewarding challenges in computer graphics. When executed properly, physics creates an immediate sense of tactile realism and playful interactivity.

However, many frontend developers struggle when their simulations encounter real-world browser conditions: games running at double speed on 120Hz/144Hz gaming monitors, objects tunneling through collision boundaries during frame drops, or visible micro-stutter (jank) caused by garbage collection pauses. In this guide, we explore the mathematics of numerical integration, explain why Euler integration fails, and dissect the architecture behind our Gravity Pulse canvas simulation.

1. The Problem with Explicit Euler Integration

The simplest way to update a moving object's position in physics code is standard Explicit Euler integration:

// NAIVE EXPLICIT EULER: Highly unstable with springs or orbital mechanics
velocity.x += acceleration.x * dt;
velocity.y += acceleration.y * dt;

position.x += velocity.x * dt;
position.y += velocity.y * dt;

While intuitive, Euler integration introduces systematic mathematical error on every single step. In physical reality, acceleration curves continuously; Euler integration approximates this curve using a sequence of straight tangent lines. When simulating harmonic oscillators (like springs) or planetary orbits, Euler integration continuously injects artificial kinetic energy into the system. Over time, orbits expand uncontrollably, springs explode into infinity, and particles shatter through solid walls.

2. The Elegance of Verlet Integration

In 1967, French physicist Loup Verlet developed a numerical integration method widely adopted in molecular dynamics, astrophysics, and video game physics (including the original Hitman game's ragdoll engine).

Instead of storing explicit velocity vectors, Verlet Integration determines motion by comparing an object's current position with its position in the previous frame:

// Verlet Position Update Formula
x_(t + dt) = 2 * x_t - x_(t - dt) + a_t * (dt)^2

Notice that velocity is implicitly encoded as (x_t - x_(t - dt))! This delivers several profound mathematical benefits:

Verlet in JavaScript

function updateVerlet(particle, dt, gravity) {
    const vx = (particle.x - particle.oldX) * particle.friction;
    const vy = (particle.y - particle.oldY) * particle.friction;

    particle.oldX = particle.x;
    particle.oldY = particle.y;

    particle.x += vx + particle.ax * dt * dt;
    particle.y += vy + (particle.ay + gravity) * dt * dt;
}

3. Taming the Display Refresh Rate: Fix Your Timestep!

Modern displays operate at varying refresh rates: 60Hz (16.6ms), 90Hz (11.1ms), 120Hz (8.3ms), or 240Hz (4.1ms). If an engine ties physical updates directly to the requestAnimationFrame callback without tracking delta time, the simulation runs twice as fast on a 120Hz iPad Pro as it does on a standard 60Hz laptop.

Conversely, passing variable delta time directly into physics equations (dt = currentTime - lastTime) causes instability whenever a background task causes a sudden frame spike (a 100ms stutter can propel a particle clear across the screen, tunneling through walls).

The industry gold standard—popularized by game developer Glenn Fiedler in his seminal article "Fix Your Timestep!"—is Delta Time Accumulation with a Fixed Physics Timestep:

let lastTime = performance.now();
let accumulator = 0;
const FIXED_STEP = 1000 / 60; // Fixed 60Hz physics (16.666ms)

function gameLoop(currentTime) {
    let frameTime = currentTime - lastTime;
    lastTime = currentTime;

    // Prevent spiral of death on tab refocus
    if (frameTime > 250) frameTime = 250;

    accumulator += frameTime;

    // Run physics in deterministic, fixed chunks
    while (accumulator >= FIXED_STEP) {
        updatePhysics(FIXED_STEP / 1000);
        accumulator -= FIXED_STEP;
    }

    // Render as fast as monitor can refresh (interpolated if needed)
    renderCanvas();

    requestAnimationFrame(gameLoop);
}

4. HTML5 Canvas Rendering Optimizations

Executing physics math is rarely the bottleneck in JavaScript; rendering thousands of pixels to the canvas context is what causes frame drops. High-performance canvas engines employ three optimization rules:

  1. Minimize Context State Changes: Calling ctx.fillStyle, ctx.strokeStyle, or ctx.shadowBlur forces the GPU driver to flush command buffers. Batch all particles sharing the same color into a single beginPath() and fill() call.
  2. Clear Rect vs. Fill Rect: ctx.clearRect(0, 0, width, height) is generally faster than ctx.fillRect() because modern GPU drivers recognize clearRect as a buffer discard command.
  3. Offscreen Canvas Prerendering: For complex particle shapes or glows, render the particle once to an offscreen canvas and use ctx.drawImage(offscreenCanvas, x, y) rather than drawing paths procedurally on every frame.

Conclusion

High-frame-rate web graphics are achieved through the marriage of numerical mathematics and browser lifecycle discipline. By replacing volatile Euler equations with Verlet integration, decoupling physical simulation from monitor refresh rates, and batching canvas draw calls, developers can engineer interactive simulations that maintain an unwavering 60 or 120 FPS across any device.

To experience real-time gravitational particle physics, orbital mechanics, and interactive tactile force fields directly in your browser, explore our free Gravity Pulse simulation.

Sudarshan Babar

Sudarshan Babar

Founder & Lead Developer at getinfotoyou. Specialized in full-stack web architecture, client-side algorithm engineering, and transparent personal finance modeling.