Using Matter.js to render to the DOM or React

旧巷老猫 提交于 2021-01-05 11:31:11

问题


I want to render custom HTML elements as Bodies in Matter.js. I am using it in React which adds a bit of complexity but it's irrelevant to my issue.

I've searched a lot and the only example I found was this one here, which seems to use querySelector to select the elements that live in the HTML code then somehow use them inside the rectangle shapes.

The part that seems to be doing the job is the following:

var bodiesDom = document.querySelectorAll('.block');
var bodies = [];
for (var i = 0, l = bodiesDom.length; i < l; i++) {
    var body = Bodies.rectangle(
        VIEW.centerX,
        20, 
        VIEW.width*bodiesDom[i].offsetWidth/window.innerWidth, 
        VIEW.height*bodiesDom[i].offsetHeight/window.innerHeight
    );
    bodiesDom[i].id = body.id;
    bodies.push(body);
}
World.add(engine.world, bodies);

(the VIEW variables there could be just random numbers as they define the shape)

However, I cannot understand how to pass an HTML element inside the Bodies rectangle as in the example above.

Ideally, I want to have complex HTML elements interacting with the physics world (like a small box with buttons, etc).

Any ideas on how this could be achieved? Or, can you explain the method used in the example that seems to have managed it?


回答1:


However, I cannot understand how to pass an HTML element inside the Bodies rectangle as in the example above.

This isn't quite what the example does. When running headlessly, Matter.js handles the physics without having any idea how it's rendered. Per animation frame, you can use the current positions of the MJS bodies and reposition your elements (or draw on canvas, etc) to reflect MJS's view of the world.

Providing a root element to MJS does seem to break the one-way data flow, but this is only for informing MJS about events like mouse position and clicks--not to be confused with rendering.

Here's a minimal example to hopefully make this clearer:

const engine = Matter.Engine.create();  
const box = {
  body: Matter.Bodies.rectangle(150, 0, 40, 40),
  elem: document.querySelector("#box"),
  render: function () {
    const {x, y} = this.body.position;
    this.elem.style.top = `${y - 20}px`;
    this.elem.style.left = `${x - 20}px`;
    this.elem.style.transform = `rotate(${this.body.angle}rad)`;
  },
};
const ground = Matter.Bodies.rectangle(
  200, 200, 400, 120, {isStatic: true}
);
const mouseConstraint = Matter.MouseConstraint.create(
  engine, {element: document.body}
);
Matter.World.add(
  engine.world, [box.body, ground, mouseConstraint]
);
(function rerender() {
  box.render();
  Matter.Engine.update(engine);
  requestAnimationFrame(rerender);
})();
#box {
  position: absolute;
  background: #111;
  height: 40px;
  width: 40px;
  cursor: move;
}

#ground {
  position: absolute;
  background: #666;
  top: 140px;
  height: 120px;
  width: 400px;
}

html, body {
  position: relative;
  height: 100%;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.14.2/matter.min.js" integrity="sha512-pi0tSRZdlNRZeANPwdAIHRAYg6gZZV6QlAiyHXn5TYqLzBKE9jlttO/QgYLMhISD6oNv2kPsVelx+n5nw0FqKA==" crossorigin="anonymous"></script>
<div id="box"></div>
<div id="ground"></div>

React

React changes the workflow but the fundamental concept is the same--MJS body data flows one-directionally from the MJS back-end to the rendering front-end, so from MJS' perspective, everything is identical as the vanilla example above. Most of the work is setting up refs and useEffect properly for use with requestAnimationFrame.

#box {
  position: absolute;
  background: #111;
  height: 40px;
  width: 40px;
  cursor: move;
}

#ground {
  position: absolute;
  top: 140px;
  height: 120px;
  width: 400px;
  background: #666;
}

html, body {
  position: relative;
  height: 100%;
  margin: 0;
}
<script type="text/babel" defer>
const {Fragment, useEffect, useRef} = React;

const Scene = () => {
  const requestRef = useRef();
  const boxRef = useRef();
  const groundRef = useRef();

  const animate = () => {
    const engine = Matter.Engine.create();
    
    const box = {
      body: Matter.Bodies.rectangle(150, 0, 40, 40),
      elem: boxRef.current,
      render: function () {
        const {x, y} = this.body.position;
        this.elem.style.top = `${y - 20}px`;
        this.elem.style.left = `${x - 20}px`;
        this.elem.style.transform = `rotate(${this.body.angle}rad)`;
      },
    };
    const ground = Matter.Bodies.rectangle(
      200, 200, 400, 120, {isStatic: true}
    );
    const mouseConstraint = Matter.MouseConstraint.create(
      engine, {element: document.body}
    );
    Matter.World.add(
      engine.world, [box.body, ground, mouseConstraint]
    );
    
    (function rerender() {
      box.render();
      Matter.Engine.update(engine);
      requestRef.current = requestAnimationFrame(rerender);
    })();
  };
  
  useEffect(() => {
    animate();
    return () => cancelAnimationFrame(requestRef.current);
  }, []);
  
  return (
    <Fragment>
      <div id="box" ref={boxRef}></div>
      <div id="ground" ref={groundRef}></div>
    </Fragment>
  );
};

ReactDOM.render(<Scene />, document.body);

</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.14.2/matter.min.js" integrity="sha512-pi0tSRZdlNRZeANPwdAIHRAYg6gZZV6QlAiyHXn5TYqLzBKE9jlttO/QgYLMhISD6oNv2kPsVelx+n5nw0FqKA==" crossorigin="anonymous"></script>

Note that these are only proofs-of-concept. More work setting up abstractions will likely be necessary before they can support more involved use cases.



来源:https://stackoverflow.com/questions/63906218/using-matter-js-to-render-to-the-dom-or-react

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!