How to Create Elastic Rubber Bands in Matter.js
This article explains how to simulate realistic rubber bands in Matter.js that exert pulling forces exclusively when extended beyond their resting length and go slack when compressed. By default, standard constraints in Matter.js resist both extension and compression, acting more like rigid rods or bidirectional springs. Achieving true rubber band behavior requires conditionally modifying the constraint properties or manually applying elastic forces within the engine's update cycle.
The Limitation of Default Constraints
A standard Matter.Constraint maintains a fixed target
length. If two connected bodies get closer than this
length, the constraint pushes them apart; if they move farther apart, it
pulls them together. A real rubber band has no compressive resistance—it
should buckle or become slack when the distance between anchor points is
less than its resting length.
Method 1: Dynamically Toggling Constraint Stiffness
The cleanest and most stable way to simulate rubber band physics is
to listen to the beforeUpdate event of the Matter.js
engine. On each tick, measure the Euclidean distance between the two
connected points. If the distance is greater than the resting length,
set the constraint's stiffness to your desired elasticity;
otherwise, set it to 0 so it goes completely slack.
const { Engine, Render, Runner, Bodies, Composite, Constraint, Events, Vector } = Matter;
// Create engine and world
const engine = Engine.create();
const world = engine.world;
// Define bodies
const anchor = Bodies.circle(400, 100, 10, { isStatic: true });
const weight = Bodies.circle(400, 250, 20);
// Define rubber band parameters
const restingLength = 100;
const bandStiffness = 0.05;
// Create constraint with initial length
const rubberBand = Constraint.create({
bodyA: anchor,
bodyB: weight,
length: restingLength,
stiffness: bandStiffness,
render: {
strokeStyle: '#e67e22',
lineWidth: 3
}
});
Composite.add(world, [anchor, weight, rubberBand]);
// Adjust stiffness every frame based on distance
Events.on(engine, 'beforeUpdate', () => {
const pointA = rubberBand.bodyA ? rubberBand.bodyA.position : rubberBand.pointA;
const pointB = rubberBand.bodyB ? rubberBand.bodyB.position : rubberBand.pointB;
// Calculate current distance between endpoints
const distance = Vector.magnitude(Vector.sub(pointB, pointA));
if (distance > restingLength) {
// Band is stretched: restore tension
rubberBand.stiffness = bandStiffness;
} else {
// Band is slack: disable tension
rubberBand.stiffness = 0;
}
});Method 2: Manual Force Application (Hooke's Law)
If you need finer control over custom elasticity curves, non-linear
stretching, or custom damping, omit the Constraint object
entirely and apply forces directly via Body.applyForce.
Hooke’s Law calculates the tension force as:
F = -k * (x - x0)
Where:
kis the spring constant (stiffness).xis the current distance between the two bodies.x0is the resting length.
Events.on(engine, 'beforeUpdate', () => {
const posA = anchor.position;
const posB = weight.position;
const delta = Vector.sub(posB, posA);
const distance = Vector.magnitude(delta);
if (distance > restingLength) {
const stretch = distance - restingLength;
const forceMagnitude = stretch * 0.001; // Adjust spring constant as needed
const forceDirection = Vector.normalise(delta);
// Force pulling weight toward anchor
const tensionForce = Vector.mult(forceDirection, -forceMagnitude);
Matter.Body.applyForce(weight, weight.position, tensionForce);
if (!anchor.isStatic) {
// Apply equal and opposite reaction force if anchor can move
Matter.Body.applyForce(anchor, anchor.position, Vector.neg(tensionForce));
}
}
});Rendering Slack Rubber Bands
When using dynamic stiffness, Matter.js renders the constraint as a straight line regardless of whether it is under tension or slack. To make slack bands visually convincing:
- Disable the constraint's default rendering by setting
render.visible = false. - Use an
afterRenderevent listener to draw a custom path using the Canvas API. - If
distance > restingLength, draw a straight line. - If
distance <= restingLength, draw a quadratic or Bézier curve that droops downward under simulated gravity to mimic a drooping cord.