Matter.js Drag-and-Throw Physics Playground — Free Interactive Snippet
Matter.js Drag-and-Throw Physics Playground · Animations · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="pg">
<div class="pg-bar">
<div class="pg-group" role="group" aria-label="Add shapes">
<button type="button" data-add="box">+ Box</button>
<button type="button" data-add="ball">+ Ball</button>
<button type="button" data-add="poly">+ Hexagon</button>
<button type="button" data-add="stack">+ Stack</button>
</div>
<label>Gravity <select id="pgGravity"><option value="0,1">Down</option><option value="0,-1">Up</option><option value="1,0">Right</option><option value="0,0">Zero-G</option><option value="0,0.3">Moon</option></select></label>
<label>Speed <input type="range" id="pgSpeed" min="0.1" max="1.5" step="0.1" value="1"><output id="pgSpeedOut">1.0×</output></label>
<button type="button" id="pgClear" class="ghost">Clear</button>
<span class="pg-stats" id="pgStats"></span>
</div>
<div class="pg-stage" id="pgStage"></div>
<p class="pg-hint">Drag shapes to throw them · double-click empty space to drop a random shape</p>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#fafaf9;color:#1c1917;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:18px}
.pg{width:100%;max-width:980px}
.pg-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px;font-size:12px;font-weight:600;color:#44403c}
.pg-group{display:flex;gap:6px}
.pg button{border:0;border-radius:9px;background:#1c1917;color:#fff;font:700 12px system-ui;padding:8px 12px;cursor:pointer}
.pg button.ghost{background:#fff;color:#1c1917;border:1px solid #d6d3d1}
.pg label{display:flex;align-items:center;gap:6px}
.pg select{font:600 12px system-ui;border:1px solid #d6d3d1;border-radius:7px;padding:5px}
.pg input[type=range]{width:100px;accent-color:#f97316}
.pg output{font:700 12px ui-monospace,monospace;width:34px}
.pg :focus-visible{outline:2px solid #f97316;outline-offset:2px}
.pg-stats{margin-left:auto;font:600 12px ui-monospace,monospace;color:#78716c}
.pg-stage{border-radius:16px;overflow:hidden;background:#fff;border:1px solid #e7e5e4;box-shadow:0 10px 30px rgba(28,25,23,.06)}
.pg-stage canvas{display:block;width:100%;height:auto;cursor:grab;touch-action:none}
.pg-hint{font-size:12px;color:#78716c;margin-top:8px;text-align:center}var M = Matter, Engine = M.Engine, Render = M.Render, Runner = M.Runner, Bodies = M.Bodies, Body = M.Body,
Composite = M.Composite, Composites = M.Composites, Mouse = M.Mouse, MouseConstraint = M.MouseConstraint, Events = M.Events, Query = M.Query;
var W = 980, H = 520;
var PALETTE = ['#f97316', '#0ea5e9', '#a855f7', '#22c55e', '#eab308', '#ec4899'];
var engine = Engine.create();
var render = Render.create({
element: document.getElementById('pgStage'),
engine: engine,
options: { width: W, height: H, wireframes: false, background: 'transparent', pixelRatio: window.devicePixelRatio || 1 },
});
var wallOpts = { isStatic: true, render: { fillStyle: '#e7e5e4' } };
Composite.add(engine.world, [
Bodies.rectangle(W / 2, H + 25, W + 100, 50, wallOpts),
Bodies.rectangle(W / 2, -25, W + 100, 50, wallOpts),
Bodies.rectangle(-25, H / 2, 50, H + 100, wallOpts),
Bodies.rectangle(W + 25, H / 2, 50, H + 100, wallOpts),
Bodies.rectangle(W * 0.72, H * 0.62, 220, 16, { isStatic: true, angle: -0.18, render: { fillStyle: '#d6d3d1' } }),
]);
function style(i) {
var c = PALETTE[i % PALETTE.length];
return { fillStyle: c, strokeStyle: '#1c1917', lineWidth: 0 };
}
var n = 0;
function make(type, x, y) {
x = x || 120 + Math.random() * (W - 240);
y = y || 60;
var opts = { restitution: 0.3, friction: 0.4, render: style(n++) };
if (type === 'box') return Bodies.rectangle(x, y, 40 + Math.random() * 40, 40 + Math.random() * 40, Object.assign(opts, { chamfer: { radius: 6 } }));
if (type === 'ball') return Bodies.circle(x, y, 18 + Math.random() * 20, Object.assign(opts, { restitution: 0.6 }));
if (type === 'poly') return Bodies.polygon(x, y, 6, 26 + Math.random() * 12, opts);
// Composites.stack lays out rows × columns of bodies from a callback.
return Composites.stack(x - 60, y, 4, 3, 2, 2, function (sx, sy) { return Bodies.rectangle(sx, sy, 30, 30, { chamfer: { radius: 4 }, render: style(n++) }); });
}
function add(type, x, y) { Composite.add(engine.world, make(type, x, y)); }
document.querySelectorAll('[data-add]').forEach(function (b) { b.addEventListener('click', function () { add(b.dataset.add); }); });
// Gravity is a vector on the engine; scale is its strength.
document.getElementById('pgGravity').addEventListener('change', function (e) {
var v = e.target.value.split(',').map(Number);
engine.gravity.x = v[0];
engine.gravity.y = v[1];
// Bodies that came to rest can be asleep; with sleeping enabled they
// would ignore a new gravity until woken. (Sleeping is off here.)
});
// timeScale slows or speeds up the whole simulation without changing the
// physics: 0.2 is slow motion, 1 is real time.
var speed = document.getElementById('pgSpeed');
speed.addEventListener('input', function () {
engine.timing.timeScale = Number(speed.value);
document.getElementById('pgSpeedOut').textContent = Number(speed.value).toFixed(1) + '×';
});
document.getElementById('pgClear').addEventListener('click', function () {
Composite.allBodies(engine.world).forEach(function (b) { if (!b.isStatic) Composite.remove(engine.world, b, true); });
Composite.allComposites(engine.world).forEach(function (c) { if (!Composite.allBodies(c).length) Composite.remove(engine.world, c, true); });
});
var mouse = Mouse.create(render.canvas);
var mc = MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 0.2, render: { visible: false } } });
['mousewheel', 'DOMMouseScroll', 'wheel'].forEach(function (t) { mouse.element.removeEventListener(t, mouse.mousewheel); });
Composite.add(engine.world, mc);
render.mouse = mouse;
// Double-click on empty space drops a random shape there. Query.point finds
// bodies under a point, so clicks on a shape don't spawn a new one.
render.canvas.addEventListener('dblclick', function () {
var p = mouse.position;
if (Query.point(Composite.allBodies(engine.world), p).length) return;
add(['box', 'ball', 'poly'][Math.floor(Math.random() * 3)], p.x, p.y);
});
// Show body count and simulation FPS.
var frames = 0, last = performance.now();
Events.on(engine, 'afterUpdate', function () {
frames++;
var now = performance.now();
if (now - last > 500) {
var bodies = Composite.allBodies(engine.world).filter(function (b) { return !b.isStatic; }).length;
document.getElementById('pgStats').textContent = bodies + ' bodies · ' + Math.round(frames * 1000 / (now - last)) + ' steps/s';
frames = 0; last = now;
}
});
Render.run(render);
Runner.run(Runner.create(), engine);
['stack', 'ball', 'poly', 'box', 'ball', 'box'].forEach(function (t, i) { setTimeout(function () { add(t, 140 + i * 130, 60); }, i * 180); });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
A Matter.js Playground — The Core API in One Toy

The fastest way to understand a physics engine is to throw things around in it. This playground wraps the parts of Matter.js you use in nearly every project — creating bodies, walls, mouse dragging, gravity and time — into one small toy, with each control mapped to a single API.
Engine, renderer, runner
Engine.create() holds the world and steps the simulation. Render.create() draws it on a canvas (wireframes: false enables colours), and Runner.run() advances the engine every animation frame. Everything added with Composite.add(engine.world, ...) becomes part of the simulation.
Bodies and composites
Bodies.rectangle (with a chamfer for rounded corners), Bodies.circle and Bodies.polygon create single bodies with options such as restitution (bounciness) and friction. Composites.stack lays out a grid of bodies from a callback — the "Stack" button drops twelve boxes at once. Walls are static bodies placed just outside the visible area.
Gravity is just a vector
engine.gravity.x and .y set the direction. Up, sideways, zero and a weak moon gravity are all one assignment each. (If you enable sleeping for performance, resting bodies must be woken to feel a new gravity.)
Time scale
engine.timing.timeScale slows or speeds the whole simulation without changing its behaviour: 0.2 is smooth slow motion, which is handy for inspecting collisions.
Dragging and spawning
MouseConstraint lets you grab and throw any body; its wheel listeners are removed so the page still scrolls over the canvas. Double-clicking empty space uses Query.point to check that nothing is under the cursor, then drops a random shape there.
Watching performance
The stats line counts non-static bodies and simulation steps per second, so you can see how the engine copes as you add dozens of stacks.
Step by step
How to Use
- 1Add shapesUse the buttons or double-click empty space.
- 2Throw themDrag any shape and release while moving.
- 3Change gravityDown, up, right, zero-G or moon.
- 4Slow timeDrag Speed to 0.2× for slow motion.
- 5Stress testAdd stacks and watch the body count and steps per second.
Real-world uses
Common Use Cases
MouseConstraint with a scroll fix.engine.timing.timeScale to study collisions.Got questions?
Frequently Asked Questions
Create an engine with Engine.create(), a renderer with Render.create({ element, engine, options }), add bodies with Composite.add(engine.world, bodies), then start Render.run(render) and Runner.run(Runner.create(), engine).
Set engine.gravity.x and engine.gravity.y. For example x: 0, y: -1 makes things fall upward, and 0, 0 gives zero gravity. engine.gravity.scale adjusts overall strength.
Set engine.timing.timeScale to a value below 1, such as 0.2. The simulation runs slower but behaves the same way.
Create a Mouse on the canvas and a MouseConstraint for the engine, add the constraint to the world, and set render.mouse. Remove the mouse's wheel listeners if the page must scroll over the canvas.
Use Query.point(bodies, point), which returns every body containing that point.