How to Bind Svelte Stores to Matter.js Updates
Integrating Matter.js with Svelte allows you to drive reactive web
interfaces using real-world 2D physics. This guide demonstrates how to
synchronize Matter.js engine updates directly with Svelte reactive
stores using Matter.js event hooks. By bridging the physics runner's
tick lifecycle with Svelte's writable stores, you can build
declarative components that react automatically to physical movements,
collisions, and state changes.
The Synchronization Architecture
Matter.js operates on an imperative loop driven by
Matter.Runner or requestAnimationFrame,
continuously updating physical properties such as position, velocity,
and rotation. Svelte operates declaratively, rendering updates when
internal stores change.
To connect the two systems efficiently:
- Initialize a Svelte
writablestore containing the state of the physics objects you want to track. - Hook into the Matter.js engine lifecycle using
Matter.Events.on(engine, 'afterUpdate', callback). - Read the transformed coordinates and rotation angles from target
Matter.Bodyinstances on every frame. - Push those updates into the Svelte store using
store.set(), triggering fine-grained reactivity in the DOM or an SVG/Canvas layer.
Implementation Example
The following pattern demonstrates how to bind physics bodies to a Svelte store inside a component's lifecycle:
<script>
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
import Matter from 'matter-js';
const { Engine, Runner, Bodies, Composite, Events } = Matter;
// Create a reactive store to hold body transformations
const boxState = writable({ x: 0, y: 0, angle: 0 });
onMount(() => {
// 1. Create engine and world
const engine = Engine.create();
const world = engine.world;
// 2. Create physics bodies
const box = Bodies.rectangle(200, 100, 50, 50, { restitution: 0.8 });
const ground = Bodies.rectangle(200, 400, 400, 40, { isStatic: true });
Composite.add(world, [box, ground]);
// 3. Bind engine updates to the Svelte store
Events.on(engine, 'afterUpdate', () => {
boxState.set({
x: box.position.x,
y: box.position.y,
angle: box.angle
});
});
// 4. Start the simulation
const runner = Runner.create();
Runner.run(runner, engine);
// 5. Cleanup on component destroy
return () => {
Runner.stop(runner);
Events.off(engine, 'afterUpdate');
Composite.clear(world, false);
Engine.clear(engine);
};
});
</script>
<!-- Render reactive element driven by the store -->
<div class="viewport">
<div
class="physics-box"
style="transform: translate({$boxState.x}px, {$boxState.y}px) rotate({$boxState.angle}rad);"
></div>
<div class="ground"></div>
</div>
<style>
.viewport {
position: relative;
width: 400px;
height: 400px;
overflow: hidden;
border: 1px solid #ccc;
}
.physics-box {
position: absolute;
top: -25px; /* Offset center point */
left: -25px;
width: 50px;
height: 50px;
background: #ff3e00;
will-change: transform;
}
.ground {
position: absolute;
top: 380px;
left: 0;
width: 400px;
height: 40px;
background: #333;
}
</style>
Performance Considerations
While updating a Svelte store every frame works smoothly for a small number of bodies, high object counts can degrade performance due to frequent reactivity cycles. To optimize throughput:
- Batch Store Updates: When tracking multiple bodies, store their coordinates inside an array or map within a single store update rather than emitting individual updates per object.
- Filter Unchanged State: Check
body.isSleepingor compare current values against previous values before callingstore.set()to eliminate redundant store updates when bodies come to rest. - Utilize Hardware Acceleration: Use CSS
transformviatranslate3dalong withwill-change: transformon target DOM nodes to ensure Svelte's reactive bindings leverage GPU rendering.