Deactivate Off-Screen Bodies in Matter.js

This article explains how to optimize performance in a Matter.js physics simulation by deactivating off-screen bodies. You will learn how to detect when a body leaves the active viewport and how to disable its physics calculations using either the Matter.Sleeping module or the isStatic property, reducing unnecessary CPU load during intensive simulations.

Why Deactivate Off-Screen Bodies

When running simulations with large worlds or many entities, computing collisions and motion for objects outside the visible camera view wastes processing power. Deactivating these bodies pauses their computational overhead until they re-enter the screen.

In Matter.js, you have two primary methods to deactivate a body:

  1. Sleeping flags (Matter.Sleeping): Officially suspends broadphase and narrowphase collision checks while preserving internal momentum states.
  2. Toggling isStatic: Converts the dynamic body into an immovable static body, stopping all gravitational and force integration.

Method 1: Using the Sleeping Module

Matter.js has built-in sleeping support. Sleeping bodies are skipped during broadphase collision checks, making this the cleanest and most idiomatic method.

1. Enable Sleeping on the Engine

Before bodies can sleep, enable the feature on your engine instance:

const engine = Matter.Engine.create({
  enableSleeping: true
});

2. Put Off-Screen Bodies to Sleep

Use Matter.Sleeping.set(body, isSleeping) to manually force a body to sleep or wake it up:

// Put body to sleep
Matter.Sleeping.set(body, true);

// Wake body up
Matter.Sleeping.set(body, false);

Method 2: Toggling isStatic

If you do not want to enable global sleeping or need the body to act as a solid, immovable obstacle while off-screen, you can toggle isStatic.

Caveat with Velocity

Setting isStatic: true sets the body's velocity and angular velocity to zero. If you want the body to resume its previous trajectory when returning to the screen, store its velocities before making it static:

// Deactivating
body.savedVelocity = { ...body.velocity };
body.savedAngularVelocity = body.angularVelocity;
Matter.Body.setStatic(body, true);

// Reactivating
Matter.Body.setStatic(body, false);
Matter.Body.setVelocity(body, body.savedVelocity);
Matter.Body.setAngularVelocity(body, body.savedAngularVelocity);

Implementing the Viewport Check Loop

To automate deactivation, monitor your bodies' positions relative to the screen boundaries during each update cycle using the beforeUpdate event.

const viewport = {
  minX: 0,
  maxX: 800,
  minY: 0,
  maxY: 600
};

Matter.Events.on(engine, 'beforeUpdate', () => {
  const bodies = Matter.Composite.allBodies(engine.world);

  for (let i = 0; i < bodies.length; i++) {
    const body = bodies[i];

    // Skip permanent static geometry like world boundaries
    if (body.isPermanentStatic) continue;

    const isOffScreen = 
      body.bounds.max.x < viewport.minX ||
      body.bounds.min.x > viewport.maxX ||
      body.bounds.max.y < viewport.minY ||
      body.bounds.min.y > viewport.maxY;

    if (isOffScreen && !body.isSleeping) {
      Matter.Sleeping.set(body, true);
    } else if (!isOffScreen && body.isSleeping) {
      Matter.Sleeping.set(body, false);
    }
  }
});

Using body.bounds ensures that the body is completely outside the visible area before deactivation occurs, preventing premature freezing while part of the body is still on-screen.