React 不会在 useEffect 的 canvas 中绘制任何东西

React won't draw anything in the canvas in useEffect

我用 matter.js 构建了一个 React 网站。我正在使用 useEffect 挂钩将内容渲染到 canvas 和 matter.js(我发现大部分代码是 here)。但是,当我尝试向 canvas 绘制任何其他内容时,什么也没有出现。所有 matter.js 相关的作品。

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>

非常感谢任何形式的帮助!

目前,您在 MJS wipes per frame 的同一个 canvas 上预先绘制了一次。因此,您绘制圆圈,然后 MJS 在呈现第一帧时立即擦除 canvas。随着引擎和渲染器 运行 向前,您永远不会尝试再次绘制。

我看到了一些解决方案(至少!)。哪个最合适取决于您的具体用例。

  1. afterRender callback for the render object 内的每一帧上绘制 canvas。这是您现在所在位置的最简单、最直接的解决方案,我在下面提供了一个示例。
  2. 在您提供给 MJS 的那个之上添加一个 second transparent canvas。这是通过绝对定位完成的。现在你可以在这个叠加层中做任何你想做的事情而不受 MJS 的干扰。
  3. 运行 MJS headlessly,这给了你最大的控制权,因为 MJS built-in 渲染器是 mostly intended for protoyping (it's a physics engine, not a graphics engine). Once you're headless, you can render whatever you want, whenever you want, however you want. I have many demos of headless rendering in my other answers, both to the plain DOM and with React and p5.js.

顺便说一句,这似乎与 React 或 useEffect 没有任何关系;即使你在 vanilla JS 中注入一个普通的 canvas 也会出现同样的问题。

此外,请记住,无论您绘制什么,除了碰巧在同一个 canvas 上之外,都与 MJS 完全无关。不要指望物理学能对它起作用,除非你使用的是 body MJS 知道的坐标。

虽然您没有提供完整的组件,但这里是上述 afterRender 方法的最小 proof-of-concept:

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",
      }
    });
    Events.on(render, "afterRender", () => {
      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();
    });
    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 () => {
      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>;
};

ReactDOM.render(<Scene />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div id="app"></div>