如何在 React.js 中使用此 Javascript 动画
How to use this Javascript animation in React.js
我有下面的代码,我想知道让它在 React.js 中运行的最佳方法。我的主要问题是我无法理解 React 中的面向对象原则。使用 JS 可以非常简单地创建我的 Particle class 的许多实例。但是 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>
拥有 classes 和相当数量的代码(游戏、动画等)不需要进行重大更改即可与 React 一起工作。
关键是重构您的代码,以便 canvas 对象可以传递给您的 animation/game 代码,而不是作为 React 不管理的全局变量。即使你不使用 React,避免全局变量也是一个好的设计。
此外,particleArray
、numberOfBalls
、createParticles
和 animate
在全局范围内是松散的,但实际上,它们一起工作来表示动画并且应该是分组为class、闭包、对象等
重构动画代码以接受 canvas 参数后,您的组件呈现 canvas 元素,将其传递到动画中并跟踪 requestAnimationFrame
以便它可以调用 cancelAnimationFrame
卸载时。
键盘和鼠标事件也由 React 管理。您可以使用正常的处理程序 onKeyDown
、onClick
、onMouseMove
等。您的游戏或动画管理器会公开函数来响应这些事件。
您还需要在 React 中处理 window 大小调整。 Rerender view on browser resize with React 中有代码,但我会使其适应挂钩并使用去抖动而不是节流。
这是对您的代码进行了最少调整的快速、不雅的草图:
body { margin: 0; }
<script type="text/babel" defer>
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.render(<Animation />, document.body);
function debounce(fn, ms) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), ms);
};
}
</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/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>
这可行,但由于前面提到的松散的全局函数和变量,设计不是很好。
从 React 的角度来看,代码可以使用自定义挂钩来处理动画帧和 window 调整大小以清理主要组件。
顺便说一句,您的 clearRect
调用混淆了宽度和高度参数,因此它只会在方形屏幕上准确清除屏幕。
为了完整起见,这里有一个简单的草图,它删除了动画全局变量,显示了处理事件并将逻辑移至挂钩。分成模块时应该是干净的。
body { margin: 0; }
<script type="text/babel" defer>
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.render(<Animation />, document.body);
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);
}, []);
}
</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/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>
我有下面的代码,我想知道让它在 React.js 中运行的最佳方法。我的主要问题是我无法理解 React 中的面向对象原则。使用 JS 可以非常简单地创建我的 Particle class 的许多实例。但是 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>
拥有 classes 和相当数量的代码(游戏、动画等)不需要进行重大更改即可与 React 一起工作。
关键是重构您的代码,以便 canvas 对象可以传递给您的 animation/game 代码,而不是作为 React 不管理的全局变量。即使你不使用 React,避免全局变量也是一个好的设计。
此外,particleArray
、numberOfBalls
、createParticles
和 animate
在全局范围内是松散的,但实际上,它们一起工作来表示动画并且应该是分组为class、闭包、对象等
重构动画代码以接受 canvas 参数后,您的组件呈现 canvas 元素,将其传递到动画中并跟踪 requestAnimationFrame
以便它可以调用 cancelAnimationFrame
卸载时。
键盘和鼠标事件也由 React 管理。您可以使用正常的处理程序 onKeyDown
、onClick
、onMouseMove
等。您的游戏或动画管理器会公开函数来响应这些事件。
您还需要在 React 中处理 window 大小调整。 Rerender view on browser resize with React 中有代码,但我会使其适应挂钩并使用去抖动而不是节流。
这是对您的代码进行了最少调整的快速、不雅的草图:
body { margin: 0; }
<script type="text/babel" defer>
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.render(<Animation />, document.body);
function debounce(fn, ms) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), ms);
};
}
</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/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>
这可行,但由于前面提到的松散的全局函数和变量,设计不是很好。
从 React 的角度来看,代码可以使用自定义挂钩来处理动画帧和 window 调整大小以清理主要组件。
顺便说一句,您的 clearRect
调用混淆了宽度和高度参数,因此它只会在方形屏幕上准确清除屏幕。
为了完整起见,这里有一个简单的草图,它删除了动画全局变量,显示了处理事件并将逻辑移至挂钩。分成模块时应该是干净的。
body { margin: 0; }
<script type="text/babel" defer>
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.render(<Animation />, document.body);
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);
}, []);
}
</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/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>