Building a Collapsible House of Cards in Matter.js
This guide explains how to construct a tall, structurally sound house of cards using the Matter.js 2D physics engine and make it tumble realistically with a slight perturbation. By fine-tuning physical properties like friction, mass, and stiffness, programmatically assembling leaning card pairs and horizontal beams, and introducing a micro-impulse, you can simulate an authentic, delicate card tower that stands stable until deliberately disturbed.
1. Engine and World Configuration
A realistic house of cards requires precise physics settings to keep thin rectangles from jittering, slipping instantly, or passing through each other.
const { Engine, Render, Runner, Bodies, Composite, Body, Vector } = Matter;
const engine = Engine.create({
positionIterations: 10,
velocityIterations: 10
});
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 800,
wireframes: false
}
});
Render.run(render);
Runner.run(Runner.create(), engine);Increasing positionIterations and
velocityIterations ensures the solver resolves collisions
accurately without tunneling.
2. Card Physical Properties
Cards need high friction to grip each other at steep angles, low mass to ensure realistic inertia, and zero restitution to eliminate unwanted bouncing during placement.
const CARD_WIDTH = 4;
const CARD_HEIGHT = 80;
const LEAN_ANGLE = 0.28; // in radians (~16 degrees)
const cardOptions = {
friction: 0.95,
frictionStatic: 1.0,
restitution: 0,
density: 0.002
};3. Procedural Card Tower Construction
A stable tier consists of pairs of leaning cards (forming an inverted "V") capped with horizontal cards that serve as the floor for the next tier.
function createCard(x, y, angle) {
return Bodies.rectangle(x, y, CARD_WIDTH, CARD_HEIGHT, {
...cardOptions,
angle: angle
});
}
function buildHouseOfCards(baseX, groundY, totalTiers) {
const cards = [];
const pairSpacing = CARD_HEIGHT * Math.sin(LEAN_ANGLE) * 2;
const tierHeight = CARD_HEIGHT * Math.cos(LEAN_ANGLE);
for (let tier = 0; tier < totalTiers; tier++) {
const currentPairs = totalTiers - tier;
const currentY = groundY - (tier * tierHeight) - (tierHeight / 2);
const tierStartX = baseX - ((currentPairs - 1) * pairSpacing) / 2;
for (let i = 0; i < currentPairs; i++) {
const pairCenterX = tierStartX + i * pairSpacing;
const cardOffset = (CARD_HEIGHT / 2) * Math.sin(LEAN_ANGLE);
// Left leaning card
cards.push(createCard(pairCenterX - cardOffset, currentY, LEAN_ANGLE));
// Right leaning card
cards.push(createCard(pairCenterX + cardOffset, currentY, -LEAN_ANGLE));
// Horizontal card platform (placed above the pair, except on top floor)
if (tier < totalTiers - 1) {
const flatCardY = currentY - (tierHeight / 2);
cards.push(Bodies.rectangle(pairCenterX, flatCardY, pairSpacing + 10, CARD_WIDTH, {
...cardOptions
}));
}
}
}
return cards;
}
// Add ground and cards to world
const ground = Bodies.rectangle(400, 780, 800, 40, { isStatic: true, friction: 1.0 });
const tower = buildHouseOfCards(400, 760, 5);
Composite.add(engine.world, [ground, ...tower]);4. Triggering the Collapse
Because the system balances via frictional contact, applying a minute force or gently nudging a base or middle card causes a dynamic load imbalance, triggering a complete structural collapse.
function applyGentleNudge(targetCard) {
Body.applyForce(
targetCard,
targetCard.position,
{ x: 0.002, y: 0 }
);
}
// Example: Trigger the collapse after the tower settles
setTimeout(() => {
// Nudge the bottom-left card
applyGentleNudge(tower[0]);
}, 2000);Setting the nudge force to a tiny horizontal vector preserves stability until the exact moment of disturbance, initiating a natural domino-style collapse throughout the entire structure.