如何在React.js中使用此Java脚本动画

v9tzhpje  于 2022-10-15  发布在  React
关注(0)|答案(1)|浏览(109)

我有下面的代码,我想知道让它在React.js中工作的最好方法。我的主要问题是,我不能理解React中的面向对象原则。用JS制作我的粒子类的许多示例是非常简单的。但怎么做才能做出React呢?如有任何帮助,我们不胜感激!

const canvas = document.getElementById('canvas1')
const ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight

class Particle {
  constructor(x, y, size, weight) {
    this.x = x
    this.y = y
    this.size = size
    this.weight = weight
  }
  update() {
    if (this.y > canvas.height) {
      this.y = 0 - this.size
      this.x = Math.random() * 60 + 200
      this.weight = Math.random() * 0.5 + 1
    }
    this.y += this.weight
    //console.log('y is inside the canvas', this.y)
  }
  draw() {
    ctx.fillStyle = 'blue'
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI)
    //ctx.fill()
    ctx.stroke()
  }
}
const particleArray = []
const numberOfBalls = 10

function createParticles() {
  for (let i = 0; i < numberOfBalls; i++) {
    const x = Math.random() * 60 + 200
    const y = Math.random() * canvas.height
    const size = Math.random() * 20 + 5
    const weight = Math.random() * 0.5 + 1
    particleArray.push(new Particle(x, y, size, weight))
  }
}
createParticles()

// animate canvas
function animate() {
  ctx.clearRect(0, 0, canvas.height, canvas.width)
  for (let i = 0; i < particleArray.length; i++) {
    particleArray[i].update()
    particleArray[i].draw()
  }
  requestAnimationFrame(animate)
}

animate()
window.addEventListener('resize', (e) => {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight
})
<canvas id="canvas1"></canvas>
whlutmcx

whlutmcx1#

拥有类和相当数量的代码(游戏、动画等)应该不需要进行重大更改即可使用Reaction。
关键是重构您的代码,以便画布对象可以传递给您的动画/游戏代码,而不是作为Reaction无法管理的全局变量。即使您没有使用Reaction,避免使用全局变量也是一个很好的设计。
此外,particleArraynumberOfBallscreateParticlesanimate在全局范围内是松散的,但实际上,所有这些都一起工作来表示动画,并且应该分组到类、闭包、对象等中。
重构动画代码以接受画布参数后,组件将呈现画布元素,将其传递到动画中并跟踪requestAnimationFrame,以便它可以在卸载时调用cancelAnimationFrame
键盘和鼠标事件也由Reaction管理。您可以使用常规处理程序onKeyDownonClickonMouseMove等。您的游戏或动画管理器将公开函数来响应这些事件。
您还需要在Reaction中处理窗口大小调整。Rerender view on browser resize with React中有代码,但我将使其适应挂钩,并使用去抖动而不是节流。
以下是对您的代码进行最小调整的快速、粗略的草图:

const {useEffect, useRef, useState} = React;

class Particle {
  constructor(canvas, x, y, size, weight) {
    this.canvas = canvas;
    this.ctx = canvas.getContext("2d");
    this.x = x;
    this.y = y;
    this.size = size;
    this.weight = weight;
  }

  update() {
    if (this.y > this.canvas.height) {
      this.y = 0 - this.size;
      this.x = Math.random() * 60 + 200;
      this.weight = Math.random() * 0.5 + 1;
    }

    this.y += this.weight;
  }

  draw() {
    const {x, y, size, ctx} = this;
    ctx.fillStyle = "blue";
    ctx.beginPath();
    ctx.arc(x, y, size, 0, 2 * Math.PI);
    ctx.stroke();
  }
}

const particleArray = [];
const numberOfBalls = 10;

function createParticles(canvas) {
  for (let i = 0; i < numberOfBalls; i++) {
    const x = Math.random() * 60 + 200;
    const y = Math.random() * canvas.height;
    const size = Math.random() * 20 + 5;
    const weight = Math.random() * 0.5 + 1;
    particleArray.push(
      new Particle(canvas, x, y, size, weight)
    );
  }
}

function animate(canvas) {
  const ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  for (let i = 0; i < particleArray.length; i++) {
    particleArray[i].update();
    particleArray[i].draw();
  }
}

const Animation = () => {
  const canvasRef = useRef();
  const requestRef = useRef();

  const update = () => {
    animate(canvasRef.current);
    requestRef.current = requestAnimationFrame(update);
  };

  const handleResize = debounce(() => {
    canvasRef.current.width = innerWidth;
    canvasRef.current.height = innerHeight;
  }, 100);

  useEffect(() => {
    createParticles(canvasRef.current);
    handleResize();
    window.addEventListener("resize", handleResize);
    requestRef.current = requestAnimationFrame(update);
    return () => {
      cancelAnimationFrame(requestRef.current);
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return <canvas ref={canvasRef}></canvas>;
};

ReactDOM.createRoot(document.querySelector("#root"))
  .render(<Animation />);

function debounce(fn, ms) {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), ms);
  };
}
body { 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>
<div id="root"></div>

这是可行的,但由于前面提到的松散的全局函数和变量,设计不是很好。
从Reaction的Angular 来看,代码可以使用自定义挂钩来处理动画帧,并调整窗口大小来清理主组件。
顺便说一句,您的clearRect调用混淆了宽度和高度参数,因此它只能在正方形屏幕上准确地清除屏幕。
为了完整,这里是一个简单的草图,它删除了动画全局变量,显示了处理事件并将逻辑移动到挂钩。分成模块时应该是干净的。

const {useEffect, useRef, useState} = React;

class Particle {
  constructor(ctx, x, y, r) {
    this.ctx = ctx;
    this.x = x;
    this.y = y;
    this.r = r;
    this.vx = 0;
    this.vy = 0;
  }

  moveTo(x, y) {
    this.x = x;
    this.y = y;
  }

  render() {
    this.ctx.beginPath();
    this.ctx.fillStyle = "white";
    this.ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
    this.ctx.fill();
  }
}

class SimpleAnimation {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext("2d");
    this.particles = [
      new Particle(this.ctx, canvas.width / 2, canvas.height / 2, 20)
    ];
  }

  onMouseMove(evt) {
    this.mouseX = evt.clientX;
    this.mouseY = evt.clientY;
  }

  tick() {
    this.particles.forEach(p => p.moveTo(this.mouseX, this.mouseY));
    this.ctx.fillStyle = "black";
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    this.particles.forEach(p => p.render());
  }
}

const Animation = () => {
  const animationRef = useRef();
  const canvasRef = useRef();

  useAnimationFrame(() => animationRef.current.tick());

  const resize = () => {
    canvasRef.current.width = innerWidth;
    canvasRef.current.height = innerHeight;
  };
  useWindowResize(resize);

  useEffect(() => {
    resize();
    animationRef.current = new SimpleAnimation(canvasRef.current);
  }, []);

  return (
    <canvas 
      onMouseMove={evt => animationRef.current.onMouseMove(evt)} 
      ref={canvasRef} 
    />
  );
};

ReactDOM.createRoot(document.querySelector("#root"))
  .render(<Animation />);

function debounce(fn, ms) {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), ms);
  };
}

function useWindowResize(fn, ms=100) {
  const handleResize = debounce(fn, ms);
  useEffect(() => {
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);
}

function useAnimationFrame(fn) {
  const rafRef = useRef();

  const animate = time => {
    fn(time);
    rafRef.current = requestAnimationFrame(animate);
  };

  useEffect(() => {
    rafRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  }, []);
}
body { 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>
<div id="root"></div>

相关问题