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:
- Shaft (Stretcher Pole): A fixed or semi-fixed vertical rod acting as the central anchor.
- Runner (Slider): A dynamic body constrained to move vertically along the shaft.
- Stretchers: Diagonal links pivoted at one end to the runner and at the other end to the main rib.
- 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:
- Opening: Apply an upward force to the runner, or directly translate its position toward the top of the shaft. As the runner moves up, it forces the stretchers outward, driving the ribs upward into an open horizontal dome.
- Closing: Apply a downward force, pulling the stretchers flat against the central shaft and collapsing the ribs downward.
// 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
- 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. - Body Density: Keep the density of the runner slightly higher than that of the ribs to ensure stable actuation without mechanical fluttering.
- 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).