Matter.js Confetti Cannon — Free Physics-Based Confetti Burst Snippet
Matter.js Confetti Cannon · Animations · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="mc-wrap">
<canvas id="mcCanvas" class="mc-canvas"></canvas>
<button class="mc-btn" id="mcFire">Launch confetti</button>
</div>*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{font-family:system-ui,-apple-system,sans-serif;background:#0b0c14;color:#fff;display:flex;align-items:center;justify-content:center;overflow:hidden}
.mc-wrap{position:relative;width:min(92vw,520px);height:min(70vh,420px);border-radius:20px;overflow:hidden;background:linear-gradient(180deg,#12142280 0%,#0b0c14 100%);border:1px solid #1f2236}
.mc-canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.mc-btn{position:absolute;left:50%;bottom:22px;transform:translateX(-50%);padding:12px 26px;border-radius:999px;border:none;background:linear-gradient(135deg,#f97316,#ec4899);color:#fff;font-family:inherit;font-size:14px;font-weight:700;cursor:pointer;box-shadow:0 8px 26px rgba(236,72,153,.4);z-index:2}
.mc-btn:active{transform:translateX(-50%) scale(.96)}const { Engine, Render, Runner, World, Bodies, Body, Events } = Matter;
const wrap = document.querySelector('.mc-wrap');
const canvas = document.getElementById('mcCanvas');
function size() {
return { w: wrap.clientWidth, h: wrap.clientHeight };
}
let { w, h } = size();
const engine = Engine.create();
engine.gravity.y = 1;
const render = Render.create({
canvas,
engine,
options: {
width: w,
height: h,
wireframes: false,
background: 'transparent',
},
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
// Floor and side walls so confetti bounces and settles instead of falling
// off the bottom of the canvas forever.
function makeBounds(w, h) {
const t = 40;
return [
Bodies.rectangle(w / 2, h + t / 2, w + t * 2, t, { isStatic: true, render: { visible: false } }),
Bodies.rectangle(-t / 2, h / 2, t, h + t * 2, { isStatic: true, render: { visible: false } }),
Bodies.rectangle(w + t / 2, h / 2, t, h + t * 2, { isStatic: true, render: { visible: false } }),
];
}
let bounds = makeBounds(w, h);
World.add(engine.world, bounds);
window.addEventListener('resize', () => {
const s = size();
w = s.w; h = s.h;
render.canvas.width = w;
render.canvas.height = h;
render.options.width = w;
render.options.height = h;
World.remove(engine.world, bounds);
bounds = makeBounds(w, h);
World.add(engine.world, bounds);
});
const colors = ['#f97316', '#ec4899', '#6366f1', '#22c55e', '#fbbf24', '#06b6d4', '#a78bfa'];
function launchConfetti() {
const originX = w / 2;
const originY = h - 30;
const pieces = [];
for (let i = 0; i < 60; i++) {
const isCircle = Math.random() > 0.5;
const color = colors[i % colors.length];
const size2 = 6 + Math.random() * 6;
const body = isCircle
? Bodies.circle(originX, originY, size2 / 2, {
restitution: 0.55,
friction: 0.15,
render: { fillStyle: color },
})
: Bodies.rectangle(originX, originY, size2, size2 * 0.6, {
restitution: 0.5,
friction: 0.2,
render: { fillStyle: color },
});
pieces.push(body);
}
World.add(engine.world, pieces);
pieces.forEach((body) => {
const angle = (-Math.PI / 2) + (Math.random() - 0.5) * 1.4;
// setVelocity, not applyForce: applyForce divides by each body's own
// mass to get velocity, so these deliberately tiny, randomly-sized
// pieces (mass varies with radius) would launch at wildly different,
// often absurd speeds for the same force value. setVelocity gives every
// piece a consistent, predictable launch speed regardless of its mass.
const speed = 6 + Math.random() * 8;
Body.setVelocity(body, {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed,
});
Body.setAngularVelocity(body, (Math.random() - 0.5) * 0.4);
});
// Clean up old confetti after it settles, so repeated bursts don't
// accumulate hundreds of resting bodies and slow the simulation down.
setTimeout(() => {
World.remove(engine.world, pieces);
}, 6000);
}
document.getElementById('mcFire').addEventListener('click', launchConfetti);Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Matter.js Confetti Cannon — Real Physics Confetti That Bounces and Settles

Most confetti effects fake physics with CSS keyframes and hand-tuned easing curves — see confetti button for that approach. This snippet uses Matter.js, a real 2D rigid-body physics engine, so every piece of confetti actually has mass, velocity, restitution, and collides with the floor and walls (and, briefly, each other) rather than following a pre-baked trajectory — the burst looks different every time you click it.
Engine, renderer, and runner
Matter.js separates three concerns: Engine runs the physics simulation (gravity, collisions, constraints), Render draws the current body states to a canvas, and Runner drives the engine forward on a fixed timestep loop. This snippet wires up all three against the same canvas, with engine.gravity.y = 1 set explicitly so confetti actually falls once launched, rather than floating.
Invisible bounds keep the burst contained
Three static bodies — a floor and two side walls, all with render: { visible: false } — bound the play area so confetti bounces and settles inside the canvas instead of falling through the bottom edge forever (Matter.js bodies fall infinitely without a floor to collide with). makeBounds() is recreated on window resize so the walls always match the current canvas size.
Launching a burst with real forces
launchConfetti() creates 60 mixed circle and rectangle bodies at a point near the bottom-center, each with randomized restitution (bounciness) and friction, then calls Body.applyForce with a randomized upward-ish angle and magnitude per piece — an actual physics impulse, not a CSS animation-fill-mode. Body.setAngularVelocity gives each piece independent spin. Because the engine resolves gravity, drag, and collisions every tick, pieces naturally spread, tumble, bounce off each other and the floor, and settle at different rates.
Cleanup so bursts don't accumulate
Each burst's bodies are removed from the world 6 seconds after launch via World.remove, so clicking the button repeatedly doesn't leave hundreds of resting bodies in the simulation, which would otherwise slow collision resolution down over time.
Where this fits
For a lighter CSS-only confetti burst, see confetti button or canvas confetti burst; for real physics without confetti, see physics balls, physics 2D burst, or physics props pucks — this snippet borrows the same Matter.js setup pattern and applies it to a celebratory launch instead of draggable or falling props.
Customizing it
Change engine.gravity.y for a floatier or heavier fall, tune restitution/friction per body for bouncier or stickier confetti, or increase the piece count for a denser burst (watch performance if you also increase the cleanup delay).
Step by step
How to Use
- 1Add the matter-js CDNInclude matter.min.js from the CDN panel.
- 2Paste HTML, CSS, and JSAn empty physics canvas and a launch button render.
- 3Click "Launch confetti"60 bodies launch upward with randomized force.
- 4Watch it settleGravity and collisions bring pieces to rest naturally.
- 5Click againA fresh burst launches; old pieces clean up after 6s.
- 6Resize the windowBounds rebuild so confetti stays contained.
Real-world uses
Common Use Cases
makeBounds for any effect that needs contained, colliding bodies, such as physics props and pucks.Body.applyForce impulses.Got questions?
Frequently Asked Questions
A CSS confetti burst (like the confetti button snippet) pre-calculates each particle's trajectory as a keyframe animation with fixed start and end states. This snippet instead creates real Matter.js bodies with mass, restitution, and friction, then applies actual force impulses to them — gravity, collisions with the floor and walls, and piece-to-piece contact are all resolved by the physics engine every tick, so the outcome is genuinely simulated rather than pre-authored, and no two bursts play out identically.
They're real Matter.js static bodies (isStatic: true) so confetti collides with and bounces off them, but render: { visible: false } tells Matter's renderer not to draw them, since their only job is to contain the simulation, not to be seen. Without them, confetti bodies would fall under gravity indefinitely and exit the canvas instead of settling at a visible floor.
It applies a force vector to a body at a given point, which Matter.js's engine integrates into that body's velocity over subsequent simulation steps — this is a genuine physics impulse, not a CSS transform. Because each of the 60 pieces gets its own randomized angle and magnitude, they launch in a natural-looking spread rather than a mechanically uniform fan.
Once confetti has settled at the floor, the bodies are still part of the physics world and Matter.js's collision detection still has to check them against everything else every tick. Repeated clicks without cleanup would accumulate hundreds of resting bodies, gradually slowing collision resolution down; removing each burst's pieces from the world a few seconds after it settles keeps the simulation's body count bounded.
The resize listener recreates the render canvas dimensions and rebuilds the three boundary bodies (floor and two walls) to match the new size, removing the old ones first. Confetti bodies already in flight or settled keep their existing position and velocity — they're unaffected by the resize — but the new bounds ensure any bodies launched afterward, or already resting near an old wall position, stay properly contained.