Camera Pan Effect in Matter.js Using Render Bounds

This article explains how to create a smooth camera pan effect in Matter.js by manipulating the render.bounds property. You will learn how to configure the renderer to support custom viewports, track a target object dynamically, apply linear interpolation for smooth camera transitions, and keep the view within your world boundaries.

Enabling Custom Bounds in Matter.js

By default, the Matter.js built-in canvas renderer displays a fixed coordinate space matching the canvas width and height. To pan the view, you must enable viewport bounds by setting render.options.hasBounds to true when initializing the renderer:

const render = Matter.Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false,
        hasBounds: true // Enables custom viewport rendering
    }
});

When hasBounds is enabled, Matter.js renders only the region bounded by render.bounds.min (top-left) and render.bounds.max (bottom-right).

Calculating Camera Position

To pan the camera to a specific point (such as a player body), calculate the desired minimum and maximum bounds based on the target position and your viewport dimensions.

const viewportWidth = 800;
const viewportHeight = 600;

function updateCamera(targetBody) {
    // Center the camera on the target
    const targetX = targetBody.position.x - viewportWidth / 2;
    const targetY = targetBody.position.y - viewportHeight / 2;

    render.bounds.min.x = targetX;
    render.bounds.min.y = targetY;
    render.bounds.max.x = targetX + viewportWidth;
    render.bounds.max.y = targetY + viewportHeight;
}

Adding Smooth Camera Panning (Lerp)

Snapping directly to the target can feel jarring. Applying linear interpolation (lerp) creates a smooth camera pan that catches up to moving objects gradually:

const camera = {
    x: 0,
    y: 0,
    speed: 0.05 // Lower values result in smoother, slower panning
};

function panCamera(targetBody) {
    const targetX = targetBody.position.x - viewportWidth / 2;
    const targetY = targetBody.position.y - viewportHeight / 2;

    // Linear interpolation
    camera.x += (targetX - camera.x) * camera.speed;
    camera.y += (targetY - camera.y) * camera.speed;

    // Apply interpolated positions to bounds
    render.bounds.min.x = camera.x;
    render.bounds.min.y = camera.y;
    render.bounds.max.x = camera.x + viewportWidth;
    render.bounds.max.y = camera.y + viewportHeight;
}

Clamping to World Limits

To prevent the camera from panning into empty space beyond the edges of your map, clamp the camera coordinates before assigning them to the bounds:

const worldLimits = {
    minX: 0,
    minY: 0,
    maxX: 3000,
    maxY: 2000
};

function clampCamera() {
    camera.x = Math.max(worldLimits.minX, Math.min(camera.x, worldLimits.maxX - viewportWidth));
    camera.y = Math.max(worldLimits.minY, Math.min(camera.y, worldLimits.maxY - viewportHeight));
}

Hooking into the Engine Loop

To ensure the camera updates on every frame before the renderer draws the physics state, attach the update function to the beforeRender event:

Matter.Events.on(render, 'beforeRender', () => {
    panCamera(player);
    clampCamera();
});

Using render.bounds alongside linear interpolation provides an efficient, native camera system in Matter.js without needing external rendering libraries.