I building a react website with matter.js. I am using the useEffect
hook to render stuff to the canvas with matter.js (I found most of this code here). However, when I try to draw anything else to the canvas, nothing appears. Everything matter.js-related works.
const scene = useRef()
const isDragging = useRef(false)
const engine = useRef(Engine.create())
useEffect(() => {
const cw = 1100;
const ch = 700;
const render = Render.create({
canvas: scene.current,
engine: engine.current,
options: {
width: cw,
height: ch,
wireframes: false,
background: 'transparent'
}
})
console.log("gravity " + engine.current.gravity.y + "x : " + engine.current.gravity.x)
let mouse = Mouse.create(render.canvas);
let mouseConstraint = MouseConstraint.create(engine.current, {
mouse: mouse,
constraint: {
render: {
visible: false
}
}
})
render.mouse = mouse;
World.add(engine.current.world, [
Bodies.rectangle(cw / 2, 0, cw, 20, {
isStatic: true,
density: 1
}),
Bodies.rectangle(cw / 2, ch, cw, 20, {
isStatic: true,
density: 1
}),
Bodies.rectangle(0, ch / 2, 20, ch, {
isStatic: true,
density: 1
}),
Bodies.rectangle(cw, ch / 2, 20, ch, {
isStatic: true,
density: 1
}),
mouseConstraint,
])
Runner.run(engine.current)
Render.run(render)
Events.on(mouseConstraint, "mousedown", function(event) {
handleSelections(mouseConstraint.body)
})
Events.on(mouseConstraint, "startdrag", function(event) {
isDragging.current = true
})
Events.on(mouseConstraint, "enddrag", function() {
isDragging.current = false
})
Events.on(engine.current, 'afterUpdate', function() {
countTen.current = countTen.current + 1;
if (countTen.current == 10) {
countTen.current = 0;
if (selectedObjectRef.current != null) {
setGraphingPos(selectedObjectRef.current.velocity.y * -1);
setTicks(ticks + 1)
}
}
})
// ******************* This part doesn't work **************************
scene.current.getContext('2d').beginPath();
scene.current.getContext('2d').arc(100, 100, 20, 0, 2 * Math.PI, false);
scene.current.getContext('2d').fillStyle = 'red';
scene.current.getContext('2d').fill();
// **********************************************************************
return () => {
Render.stop(render)
World.clear(engine.current.world)
Engine.clear(engine.current)
render.canvas.remove()
render.canvas = null
render.context = null
render.textures = {}
}
}, [])
<canvas ref={scene} onClick={ handleMouseDown} className='main-canvas'></canvas>
Any kind of help is much appreciated!
Currently, you're drawing one time, up front, on the same canvas that MJS wipes per frame. So, you draw your circle, then MJS wipes the canvas immediately when it renders its first frame. You never attempt to draw again as the engine and renderer run onward.
I see a few solutions (at least!). Which is most appropriate depends on your specific use case.
afterRender
callback for the render
object. This is the simplest and most direct solution from where you are now and I provide an example below.As an aside, this doesn't appear related to React or useEffect
in any way; the same problem would arise even if you were injecting a plain canvas in vanilla JS.
Also, keep in mind that whatever you draw is totally unrelated to MJS other than the fact that it happens to be on the same canvas. Don't expect physics to work on it, unless you're using coordinates from a body MJS knows about.
Although you haven't provided a full component, here's a minimal proof-of-concept of the afterRender
approach mentioned above:
const {useEffect, useRef} = React;
const {Bodies, Engine, Events, Render, Runner, Composite} = Matter;
const Scene = () => {
const canvasRef = useRef();
useEffect(() => {
const cw = 200;
const ch = 200;
const engine = Engine.create();
const ctx = canvasRef.current.getContext("2d");
const render = Render.create({
canvas: canvasRef.current,
engine,
options: {
width: cw,
height: ch,
wireframes: false,
background: "transparent",
}
});
const handleAfterRender = () => {
const x = Math.cos(Date.now() / 300) * 80 + 100;
const y = Math.sin(Date.now() / 299) * 80 + 100;
ctx.beginPath();
ctx.arc(x, y, 20, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
};
Events.on(render, "afterRender", handleAfterRender);
Composite.add(engine.world, [
Bodies.rectangle(cw / 2, 0, cw, 20, {
isStatic: true,
density: 1,
}),
Bodies.rectangle(cw / 2, ch, cw, 20, {
isStatic: true,
density: 1,
}),
Bodies.rectangle(0, ch / 2, 20, ch, {
isStatic: true,
density: 1,
}),
Bodies.rectangle(cw, ch / 2, 20, ch, {
isStatic: true,
density: 1,
}),
]);
Runner.run(engine);
Render.run(render);
return () => {
Events.off("afterRender", handleAfterRender);
Render.stop(render);
Composite.clear(engine.world);
Engine.clear(engine);
render.canvas.remove();
render.canvas = null;
render.context = null;
render.textures = {};
};
}, []);
return <canvas ref={canvasRef}></canvas>;
};
const root = document.querySelector("#app");
ReactDOM.createRoot(root).render(<Scene />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"></script>
<div id="app"></div>