Question
Setting fillStyle works inconsistently for bodies in Matter.JS
I'm working with Matter.JS for an interactive header graphic on a website. I have some basic letter paths in SVG, which get loaded, converted to shapes and bodies and placed in the world. They get initial colors and properties and they animate fine.
The problem is, I wanted the bodies to be interactive, in that when a user clicks one of the bodies, it can change. I used a MouseConstraint
for this and it kind of works, but only for certain properties; setting a fillStyle
(color) does only work for one (!) of the bodies (the letter "I") and can't figure out why.
I have set up an example and I'm hoping it's a simple thing to figure out for somebody who knows their stuff. I'm only working with MJS for the first time.
Matter.Common._seed = Math.random().toString().slice(2,10); // random 8 digit number
Matter.use(MatterAttractors);
const engine = Matter.Engine.create();
const currentWidth = document.querySelector('#canvas').offsetWidth * 2;
// letter initial coordinates
const offsets = {
'B': [currentWidth/100*20, currentWidth/100*5],
'I': [currentWidth/100*50, currentWidth/100*5],
'G': [currentWidth/100*70, currentWidth/100*5],
'B2': [currentWidth/100*20, currentWidth/100*50],
'A': [currentWidth/100*40, currentWidth/100*50],
'N': [currentWidth/100*60, currentWidth/100*50],
'D': [currentWidth/100*80, currentWidth/100*50]
};
var render = Matter.Render.create({
element: document.querySelector('#canvas'),
engine: engine,
options: {
width: currentWidth,
height: currentWidth,
background: '#222',
wireframes: false,
showAngleIndicator: false
}
});
var select = function (root, selector) {
return Array.prototype.slice.call(root.querySelectorAll(selector));
};
var loadSvg = function (url) {
return fetch(url)
.then(function(response) { return response.text(); })
.then(function(raw) { return (new window.DOMParser()).parseFromString(raw, 'image/svg+xml'); });
};
// add SVG letters
document.querySelectorAll('#letters path').forEach(function (ele, i) {
const color = ele.getAttribute('fill');
const id = ele.getAttribute('id').replace('letter-', '');
const vertexSets = Matter.Svg.pathToVertices(ele, 30);
const offsetX = offsets[id][0];
const offsetY = offsets[id][1];
var letter = Matter.Bodies.fromVertices(offsetX, offsetY, vertexSets, {
render: {
fillStyle: color,
strokeStyle: color,
lineWidth: 1,
opacity: 1
},
restitution: 0.7,
label: 'Letter-' + id
});
Matter.Body.rotate(letter, Matter.Common.random(-0.3, 0.3));
Matter.Body.scale(letter, 1.0, 1.0);
Matter.Body.setMass(letter, 5.01);
Matter.Composite.add(engine.world, letter, true);
});
// walls
const wallstyle = { fillStyle: '#0f0' };
var ground = Matter.Bodies.rectangle(currentWidth/2, currentWidth, currentWidth, 10, { isStatic: true, render: wallstyle });
var wallLeft = Matter.Bodies.rectangle(0, currentWidth/2, 10, currentWidth, { isStatic: true, render: wallstyle });
var wallRight = Matter.Bodies.rectangle(currentWidth, currentWidth/2, 10, currentWidth, { isStatic: true, render: wallstyle });
Matter.Composite.add(engine.world, [wallLeft, wallRight, ground]); // [boxA, boxB, ground]
Matter.Render.run(render);
var runner = Matter.Runner.create();
Matter.Events.on(runner, "tick", event => {}); // nothing as of right now
Matter.Runner.run(runner, engine);
// mouse events
const mouseConstraint = Matter.MouseConstraint.create(
engine,
Matter.Mouse.create(render.canvas),
{}
);
Matter.Events.on(mouseConstraint, "mousedown", event => {
const target = event.source.body;
if (target) {
// why is the fillStyle only working for the letter "I"?
target.render.fillStyle = Matter.Common.choose(['#f19648', '#f5d259', '#f55a3c']);
// applying force works for some reason
const dir = Matter.Common.random(-0.3, 0.3);
Matter.Body.applyForce(target, { x: target.position.x, y: target.position.y }, { x: dir, y: -0.2 });
}
});
html {
background: black;
color: #fff;
}
#canvas {
margin: 2rem auto;
width: 40vw;
max-width: 40rem;
aspect-ratio: 1 / 1;
outline: 1px solid #0ff;
}
#canvas canvas {
display: block;
width: 100%;
height: auto;
}
#letters { display: none; }
<div id="canvas"></div>
<!-- SVG letter shapes, to be loaded in JS -->
<div id="letters">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 0 200 200" xml:space="preserve">
<path id="letter-B" fill="#00A15D" d="M187.5,101c3.3-5.6,4.9-12.1,4.9-19.3v-81h-62.7h-30H7.6v198h92.1v-36.4h-1.8v-30.6h1.8V97.3 h-1.8V36.9h1.9h2.4v56.8c0,2.5-0.8,3.7-2.4,3.7h0v34.3h2.4v26.1c0,1.2-0.2,2.3-0.5,3.2c-0.4,0.9-1,1.3-1.8,1.3h0v36.4h53.1 c12.7,0,22.4-3.6,29.3-10.7c6.9-7.1,10.3-16.5,10.3-28.1v-48.8h-13.5C182.4,108.3,185.3,104.9,187.5,101z" />
<path id="letter-I" fill="#00A15D" d="M54.9 1h90.1v198H54.9Z" />
<path id="letter-G" fill="#00A15D" d="M7.5,199V39.8c0-11.6,3.4-21,10.3-28.1C24.7,4.6,34.4,1,46.9,1h145.6v38.5H102 c-1.4,0-2.5,0.5-3.2,1.5c-0.7,1-1.1,2.1-1.1,3.3v117.2h4.2V81.3V43.8h90.4V199H7.5z" />
<path id="letter-B2" fill="#F6B9AA" d="M152,102.6c3.3-5.6,4.9-12.1,4.9-19.3V1H43.1v69.5h54.6V35.8h4.2v58.9c0,2.5-0.8,3.7-2.4,3.7 h-1.8V70.3H43.1v83.5h54.6v-21.1h4.2v26.7c0,1.2-0.2,2.3-0.5,3.2c-0.4,0.9-1,1.3-1.8,1.3h-1.8v-10.8H43.1V199h74.2 c12.7,0,22.4-3.6,29.3-10.7c6.9-7.1,10.3-16.5,10.3-28.1v-48.6h-12.2C147.6,109,150,106.1,152,102.6z" />
<path id="letter-A" fill="#F6B9AA" d="M43.1,1v91.1l54.7,1v-57h4.2V124h-4.2V87.1H43.1V199h54.7v-39.6h4.2V199h54.9V1H43.1z"/>
<path id="letter-N" fill="#F6B9AA" d="M43.1 199L43.1 1L156.9 1L156.9 199L102 199L102 36.1L97.8 36.1L97.8 199z" />
<path id="letter-D" fill="#F6B9AA" d="M43,1v114.5h54.6V36.1h4.2v123.8c0,0.9-0.2,1.7-0.5,2.5c-0.4,0.8-0.8,1.2-1.3,1.2h-2.4v-52.1 H43V199h75.2c7.6,0,14.3-1.7,20.1-5c5.8-3.3,10.4-7.9,13.7-13.7c3.3-5.8,5-12.2,5-19.3V1H43z" />
</svg>
</div>
<script src="https://cdn.jsdelivr.net/npm/poly-decomp@0.2.1/build/decomp.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pathseg@1.2.1/pathseg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/matter-js@0.20.0/build/matter.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/matter-attractors@0.1.6/build/matter-attractors.min.js"></script>