The most basic way to import the MatterJS library is via the script tag. (CDN information)
1
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"></script>
(I've created a codepen that imports the library that you can use here.)
The first thing we'll need to do is set up our svg. I've created a group with an id of "holder" that will contain the particle graphic we'll eventually create. You'll notice that I've intentionally left the values for width, height, and viewBox blank - I'll be setting those values later with javascript.
1
2
3
<svg width="" height="" viewBox="">
<g id="particleHolder"></g>
</svg>
Now let's use javascript destructuring to create variables for all the MatterJS modules that we'll need for this lesson.
1
const { Engine, Body, Bodies, Composite } = Matter;
Next we'll create the physics engine. Again, the engine is responsible for all the physics calculations.
1
2
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
We'll need variables for particle body and the particle graphic. The particle body is a MatterJS object that will contain all the properties we'll need to animate the particle graphic. We'll also need a variable for the particle radius (particleRadius).
We'll be appending the dynamically created particle graphic to the group with an id of "particleHolder" in the svg, so we'll also need a variable for that.
1
2
3
4
5
6
7
8
9
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
let particleGraphic, particleBody;
const particleRadius = 20;
// holder variable
const particleHolder = document.querySelector("#particleHolder");
I'm also setting up variables for the svg properties - viewport width and height (viewportWidth, viewportHeight), and the viewBox width and height (w, h). I could just hard-code these values, but I like setting them dynamically - it makes it easier to change things around and experiment.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
let particleGraphic, particleBody;
const particleRadius = 20;
// holder variable
const particleHolder = document.querySelector("#particleHolder");
// svg variables
const viewportWidth = 500;
const viewportHeight = 500;
const w = 500;
const h = 500;
const namespace = "http://www.w3.org/2000/svg";
The first thing we need is a method to set up our SVG viewport and viewBox.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// svg variables
...
function initSVG() {
const svg = document.querySelector("svg");
svg.setAttribute("width", `${viewportWidth}`);
svg.setAttribute("height", `${viewportHeight}`);
svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
}
We'll need a method to create the particle graphic using the dynamic element creation skills we learned in an earlier lesson. The initParticleGraphic method below creates a circle with a radius of 50. The cx/cy position doesn't really matter right now, as we'll be positioning the particle later.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// svg variables
...
function initSVG() { ...}
function initParticleGraphic() {
particleGraphic = document.createElementNS(namespace, "circle");
particleGraphic.setAttribute("cx", "0");
particleGraphic.setAttribute("cy", "0");
particleGraphic.setAttribute("r", particleRadius);
particleGraphic.setAttribute("fill", "black");
particleHolder.appendChild(particleGraphic);
}
Now let's create the body. The MatterJS Bodies module has a circle method that we'll use to create a 2D body that's the same size and shape as the particle graphic. It looks like this:
Bodies.circle(x, y, radius, [options]);
The options object is used to set all kinds of physical properties on a MatterJS body. We'll just be setting three - id, friction and restitution.
id: A unique name that the engine needs to keep track of the body within the world. The id must be unique. If not, all sorts of buggieness will ensue, as we'll see in a later lesson!
friction: A number between 0-1 that defines the friction of the body.
restitution: A number between 0-1 that defines the elasticity (bounciness) of the object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// svg variables
...
function initSVG() { ...}
function initParticleGraphic() {...};
function initParticleBody() {
particleBody = Bodies.circle(0, 0, particleRadius, {
id: `particleBody`,
friction: 0,
restitution: 0.99
});
Body.setPosition(particleBody, { x: w/2, y: particleRadius })
};
Once we create the particle body, we use the setPosition method of the Body module to position the body at the top of the SVG.
Body.setPosition(body, { x: number, y: number });
Remember, the MatterJS body isn't actually rendered at all. Think of it as existing in a virtual world that we'll refer to every frame, and use it's position and orientation to position our graphic.
To prevent our particle from just falling in infinite space, we'll need to create some sort of floor body. We want this floor to be different than our particle - it needs to be completely stationary and unaffected by gravity. We'll do this by creating a static body.
We'll use the Bodies rectangle method to create the floor;
Bodies.rectangle(x, y, radius, height, [options]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// floor variables
let floor;
const floorThickness = 100;
// svg variables
...
function initSVG() { ...}
function initParticleGraphic() {...};
function initParticleBody() {...};
function initFloor() {
floor = Bodies.rectangle(w/2, h + floorThickness/2, w, floorThickness, {
isStatic: true,
id: "floor"
});
}
You might be confused by the values we have for the x and y position (x:w/2, y:h + floorThickness/2). If we're creating the floor at the bottom of the space, shouldn't x=0 and y = h (the height of the space)? Well, the way MatterJS defines rectangles is a bit different than how they're defined in SVGs, in that the x and y position represent the center of gravity of the body, and not the upper right hand corner. For rectangles, and circles, the center of gravity is always in the middle of the body. For irregularly shaped objects, the center can be elswhere, as we'll see in a later lesson.
In this example, since the "room" is 500 units wide (w), to have it span the entire width of the space we'll need to also make the floor 500 units wide, and place it horizontally at 250 (w/2). Since it's 100 units tall (floorThickness=100), and we want the top of the floor to line up with the bottom of the space, we'll need to place it vertically at h + floorThickness/2.
As you can see, instead of an eleasticity property the floor has a boolean isStatic property that is set to true. As a result, the floor isn't effected by gravity, and remains stationary.
Now we have to create the world, and put the particle and floor in it using the Composite module:
Composite.add(composite, [objects])
A Composite is just a container for a collection of objects. In this case we'll use the pre-defined engine.world as our composite, and add the particle and floor as an array of objects:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// floor variables
let floor;
const floorThickness = 100;
// svg variables
...
function initSVG() { ...}
function initParticleGraphic() {...};
function initParticleBody() {...};
function initFloor() {...};
function makeWorld() {
Composite.add(engine.world, [particleBody, floor]);
}
Now it's time to animate our graphic using our trusted update/requestAnimationFrame method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// floor variables
...
// svg variables
...
function initSVG() { ...}
function initParticleGraphic() {...};
function initParticleBody() {...};
function initFloor() {...};
function makeWorld() {...}
function update() {
// update the engine.
Engine.update(engine);
// look at the particleBody position and update graphic position accordingly.
const pos = particleBody.position;
particleGraphic.setAttribute("cx", pos.x);
particleGraphic.setAttribute("cy", pos.y);
window.requestAnimationFrame(update);
};
The update loop is pretty simple. First we call the update method on our engine, which will advance it one "frame".
Engine.update(engine);
Then we get the position data from the particleBody, and set the cx and cy attributes of the particle accordingly. We then call requestAnimationFrame to continue the loop.
Now all we need to do to kick things off is call all the methods we created above:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const { Engine, Body, Bodies, Composite } = Matter;
const engine = Engine.create();
// particle vars
...
// holder variable
...
// floor variables
...
// svg variables
...
function initSVG() { ...}
function initParticleGraphic() {...};
function initParticleBody() {...};
function initFloor() {...};
function makeWorld() {...}
function update() {
// update the engine.
Engine.update(engine);
// look at the particleBody position and update graphic position accordingly.
const pos = particleBody.position;
particleGraphic.setAttribute("cx", pos.x);
particleGraphic.setAttribute("cy", pos.y);
window.requestAnimationFrame(update);
};
initParticleGraphic();
initParticleBody();
initFloor();
makeWorld();
initUI();
update();
You can check out and play around with the code for this lesson at this codepen. There are a few minor differences to the code apove - I've added a replay button, for example - but for all intents and purposes it's identical to what we've just discussed.
Sometimes it can be useful to see what's going on in the MatterJS world as we're buiding out the svg graphics that we'll be animating, especially when we're creating complex rigid body shapes (as we'll do in an upcoming lesson). Activating the renderer only takes a few lines of code.
With our HTML, we'll need to create a div that we can attach the renderer to:
1
2
3
4
5
6
7
<div class="visHolder">
<svg id="svg" width="" height="" viewBox="">
<rect id="bg" x="0" y="0" width="" height="" />
<g id="particleHolder"></g>
</svg>
<div id="rendererHolder"></div>
</div>
In our code we need to make sure that we're importing the Render module:
1
const { Engine, Render, Body, Bodies, Composite } = Matter;
Next we'll create the renderer using the Render.create method:
Render.create({
element: someHTMLElement,
engine: engine,
options: {
width: someWidthValue,
height: someHeightValue,
showAngleIndicator: true
}
})
The options are pretty self-explanitory for the most part. The width and height properties should match the viewBox values. The showAngleIndicator indicator is a boolean value, and it makes the renderer draw a red tick mark on our objects so we can see their rotation. After we create the renderer we just need to actually tell it to start running:
Render.run(rendererName)
Here's the actual method I use in the example below to create the renderer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function initRenderer() {
// create renderer
const rendererHolder = document.querySelector("#rendererHolder");
const render = Render.create({
element: rendererHolder,
engine: engine,
options: {
width: w,
height: h,
showAngleIndicator: true
}
});
Render.run(render);
}
initSVG();
initRenderer();
initParticleGraphic();
initParticleBody();
initFloor();
makeWorld();
initUI();
update();
And that's it! You can check out what the render actually looks like in the codepen below. I'd absolutely recommend using the render while you're figuring out how to create svg elements that correspond to the MatterJS bodies. It can be tricky sometimes, and having a visual of what's really happening in the MatterJS world can be helpful.