使用Matter.js将其渲染到DOM或React上

3
我希望能够在Matter.js中将自定义HTML元素渲染为物体。我正在使用React,这增加了一些复杂性,但与我的问题无关。
我已经进行了大量搜索,唯一找到的示例是这个,它似乎使用querySelector选择存在于HTML代码中的元素,然后以某种方式在矩形形状内使用它们。
看起来实现这一功能的部分是以下内容:
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);

(这里的VIEW变量可能只是随机数字,因为它们定义了形状)

然而,我不知道如何将HTML元素传递到上述示例中Bodies矩形内部。

理想情况下,我希望有复杂的HTML元素与物理世界互动(例如具有按钮的小框等)。

有任何关于如何实现这一点的想法吗?或者,您能解释一下似乎已经成功实现的示例中使用的方法吗?

1个回答

7
然而,我不知道如何像上面的例子一样在Bodies矩形内传递HTML元素。这并不完全是示例的功能。在无头运行时,Matter.js处理物理过程而不知道它是如何呈现的。每个动画帧,您可以使用MJS身体的当前位置重新定位元素(或在画布上绘制等),以反映MJS对世界的视图。向MJS提供根元素似乎会破坏单向数据流,但这仅用于通知MJS有关鼠标位置和单击之类的事件,不能与渲染混淆。这里有一个最小的示例,希望能更清晰地说明问题:

const engine = Matter.Engine.create();  
const box = {
  body: Matter.Bodies.rectangle(150, 0, 40, 40),
  elem: document.querySelector("#box"),
  render() {
    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.Composite.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.19.0/matter.min.js"></script>
<div id="box"></div>
<div id="ground"></div>

React

React改变了工作流程,但基本概念是相同的——MJS后端的数据以单向方式流向渲染前端,因此从MJS的角度来看,一切都与上面的基础示例相同。大部分工作是为了使用RefsuseEffect并正确配合requestAnimationFrame使用。

const {Fragment, useEffect, useRef} = React;

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

  const animate = () => {
    engineRef.current = Matter.Engine.create();
    const engine = engineRef.current;

    const box = {
      body: Matter.Bodies.rectangle(150, 0, 40, 40),
      elem: boxRef.current,
      render() {
        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, // x
      200, // y
      400, // w
      120, // h
      {isStatic: true}
    );
    const mouseConstraint = Matter.MouseConstraint.create(
      engine,
      {element: document.body}
    );
    Matter.Composite.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);
      Matter.Engine.clear(engineRef.current);
      // see https://github.com/liabru/matter-js/issues/564
      // for additional cleanup if using MJS renderer/runner
    };
  }, []);

  return (
    <Fragment>
      <div id="box" ref={boxRef}></div>
      <div id="ground" ref={groundRef}></div>
    </Fragment>
  );
};

ReactDOM.createRoot(document.querySelector("#app")).render(
  <Scene />
);
#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 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.19.0/matter.min.js"></script>
<div id="app"></div>

请注意这些只是概念验证。在支持更复杂的用例之前,可能需要更多的抽象设置工作。


完美的答案。 - Lewis Morris
非常好的例子,谢谢。有没有办法以某种方式显示画布版本的调试标志,或者换句话说,将画布版本渲染为透明覆盖版本并行显示? - bluelemonade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接