Build a Folding Umbrella Mechanism in Matter.js

This guide explains how to design and simulate a functioning folding umbrella mechanism in Matter.js using rigid bodies and articulated pin constraints. You will learn the structural anatomy of an umbrella's linkage system, how to implement revolute joints using Matter.Constraint, how to create a sliding runner, and how to actuate the opening and closing states via programmatically applied forces.

The Kinematics of an Umbrella

A folding umbrella operates on a classic slider-rocker or four-bar linkage mechanism. To model this in a 2D physics engine like Matter.js, the assembly requires four primary components:

  1. Shaft (Stretcher Pole): A fixed or semi-fixed vertical rod acting as the central anchor.
  2. Runner (Slider): A dynamic body constrained to move vertically along the shaft.
  3. Stretchers: Diagonal links pivoted at one end to the runner and at the other end to the main rib.
  4. Ribs: The primary outward-extending beams pivoted at the top of the shaft (the notch) and pushed upward and outward by the stretchers.

Step 1: Initializing Matter.js and Setting Up Rigid Bodies

First, instantiate the core Matter.js modules: Engine, Render, Runner, Bodies, Composite, and Constraint.

To prevent the mechanical parts from colliding with each other and binding the joints, assign a common negative collisionFilter.group to all umbrella components.

const { Engine, Render, Runner, Bodies, Composite, Constraint, Body, Vector } = Matter;

const engine = Engine.create();
const world = engine.world;

const render = Render.create({
  element: document.body,
  engine: engine,
  options: { width: 800, height: 600, wireframes: false }
});

Render.run(render);
Runner.run(Runner.create(), engine);

// Collision group to disable self-collision among linkages
const UMBRELLA_GROUP = Body.nextGroup(true);
const defaultOptions = {
  collisionFilter: { group: UMBRELLA_GROUP },
  density: 0.005
};

Step 2: Creating the Central Shaft and Slider

The shaft acts as the base reference. The runner is a small rectangle configured to slide linearly along the shaft.

const shaftWidth = 12;
const shaftHeight = 350;
const shaftX = 400;
const shaftY = 300;

// Central static shaft
const shaft = Bodies.rectangle(shaftX, shaftY, shaftWidth, shaftHeight, {
  isStatic: true,
  ...defaultOptions,
  render: { fillStyle: '#333' }
});

// Slider (Runner) that moves along the shaft
const runner = Bodies.rectangle(shaftX, shaftY + 80, 30, 20, {
  ...defaultOptions,
  render: { fillStyle: '#e74c3c' }
});

// Constrain the runner along the vertical axis of the shaft
const sliderConstraint = Constraint.create({
  bodyA: runner,
  pointB: { x: shaftX, y: shaftY + 80 },
  stiffness: 0.05,
  damping: 0.1
});

Composite.add(world, [shaft, runner, sliderConstraint]);

Step 3: Assembling the Ribs and Stretchers

Articulated joints (revolute/pin joints) in Matter.js are constructed using Constraint.create() with a length of 0 and a stiffness of 1. By setting specific anchor offsets (pointA and pointB), you create pivot points at the ends or along the lengths of the bodies.

To create a symmetrical umbrella, mirror the linkages across both sides of the shaft:

function createUmbrellaArm(sideMultiplier) {
  const ribLength = 180;
  const stretcherLength = 100;

  // Rib (the main canopy spine)
  const rib = Bodies.rectangle(
    shaftX + (sideMultiplier * ribLength) / 2,
    shaftY - shaftHeight / 2 + 10,
    ribLength,
    8,
    { ...defaultOptions, render: { fillStyle: '#3498db' } }
  );

  // Stretcher (connects runner to rib)
  const stretcher = Bodies.rectangle(
    shaftX + (sideMultiplier * stretcherLength) / 2,
    shaftY + 20,
    stretcherLength,
    6,
    { ...defaultOptions, render: { fillStyle: '#f1c40f' } }
  );

  // Pivot 1: Top of shaft to inner end of rib
  const topPivot = Constraint.create({
    bodyA: shaft,
    pointA: { x: 0, y: -shaftHeight / 2 + 10 },
    bodyB: rib,
    pointB: { x: -sideMultiplier * (ribLength / 2), y: 0 },
    length: 0,
    stiffness: 1
  });

  // Pivot 2: Runner to inner end of stretcher
  const runnerPivot = Constraint.create({
    bodyA: runner,
    pointA: { x: sideMultiplier * 10, y: 0 },
    bodyB: stretcher,
    pointB: { x: -sideMultiplier * (stretcherLength / 2), y: 0 },
    length: 0,
    stiffness: 1
  });

  // Pivot 3: Outer end of stretcher to mid-point of rib
  const ribMidPivot = Constraint.create({
    bodyA: stretcher,
    pointA: { x: sideMultiplier * (stretcherLength / 2), y: 0 },
    bodyB: rib,
    pointB: { x: -sideMultiplier * (ribLength / 6), y: 0 },
    length: 0,
    stiffness: 1
  });

  Composite.add(world, [rib, stretcher, topPivot, runnerPivot, ribMidPivot]);
}

// Instantiate both left (-1) and right (1) sides
createUmbrellaArm(1);
createUmbrellaArm(-1);

Step 4: Actuation and Control

The folding and unfolding behavior is actuated by changing the vertical position of the runner:

// Actuation loop using engine events
let isOpen = false;

window.addEventListener('keydown', (e) => {
  if (e.code === 'Space') {
    isOpen = !isOpen;
  }
});

Matter.Events.on(engine, 'beforeUpdate', () => {
  // Constrain slider strictly to the X-centerline
  Body.setPosition(runner, { x: shaftX, y: runner.position.y });
  Body.setVelocity(runner, { x: 0, y: runner.velocity.y });

  // Drive slider based on state
  const targetY = isOpen ? shaftY - 60 : shaftY + 110;
  const deltaY = targetY - runner.position.y;
  
  // Apply proportional corrective force
  Body.applyForce(runner, runner.position, { x: 0, y: deltaY * 0.002 });
});

Tuning and Performance Optimization

  1. Constraint Iterations: Linkage mechanisms place high stress on rigid constraints. Increase engine.constraintIterations (default is 2, set to 8–10) to prevent the joints from sagging or drifting apart under tension.
  2. Body Density: Keep the density of the runner slightly higher than that of the ribs to ensure stable actuation without mechanical fluttering.
  3. Resting Stops: Add small static sensor bodies or position clamping logic at the top and bottom of the shaft to prevent over-extension and mechanical inversion (where linkages fold backward).