如何将对象传递给 JavaScript 中的动画函数?
How to pass object to animation function in JavaScript?
我一直在尝试在不使用全局变量的情况下在 HTML Canvas 中制作一个 JavaScript 移动圆圈动画。我正在使用 requestAnimationFrame 函数。由于 JavaScript 不支持通过引用传递变量,我尝试创建一个 Circle class:
class Circle{
constructor(x, y, dx, dy) //set initial position and velocity of circle
{
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
}
function moveCircle(circle, other variables)
{
//clear canvas
//calculate new position and velocity using circle.x, etc.
//save new values to the object
//draw new circle into the canvas
requestAnimationFrame(moveCircle);
}
function button()//function called after click on button
{
//initial settings of canvas, intial condition
circle = new Circle(x, y, dx, dy);
moveCircle(circle, other vars);
}
这使一帧然后抛出错误"Cannot read property 'x' of undefined"。我究竟做错了什么?有没有其他方法可以做到这一点,同时避免使用全局变量?
首先,你不需要创建一个 class 你可以只传递对象中的坐标或作为单独的参数。
其次,您应该在 requestAnimationFrame
上使用 Function#bind
将相同的参数传递给下一个调用。
使用对象的示例:
function moveCircle(circle) {
console.log(circle.x);
if (circle.x) {
circle.x -= circle.dx;
requestAnimationFrame(moveCircle.bind(null, circle));
}
}
function button() {
moveCircle({
x: 500,
y: 0,
dx: 20,
dy: 0
});
}
button();
没有对象的例子:
function moveCircle(x, dx) {
console.log(x);
if (x) {
requestAnimationFrame(moveCircle.bind(null, x - dx, dx));
}
}
function button() {
moveCircle(500, 20);
}
button();
为简单起见,您可以使用 closure
创建一个内部处理程序,它知道您的圈子是什么样子以及它应该如何移动。闭包只是一个函数,它在本地定义自己的变量,然后 return 是一个可以访问这些变量的函数。
我们想要 return 一个只接受一个参数的函数:time
,因为这是浏览器传递给 AnimationFrame 中每个处理程序的参数。然后我们希望闭包函数绘制到全局定义的 canvas 中。看这里:
const canvas = document.body.appendChild( document.createElement( 'canvas' ) );
function makeMovingCircle( canvas, x, y, radius, distance, duration ){
// Lets get the context to draw here so we only need to fetch it once and store it, saving some computing time in favour of storing into memory.
const ctx = canvas.getContext( '2d' );
// We need to return a named function here so it can interally call itself again in requestAnimationFrame
return function AnimationHandler( time ){
// Lets just calculate an offset here based on the distance and duration we passed in above.
const progress = (time % duration / duration) * distance;
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
ctx.arc( x + progress, y, radius, 0, Math.PI*2, true );
ctx.stroke();
// Now call the named handler for the next animationFrame
window.requestAnimationFrame( AnimationHandler );
}
}
// Now lets make an animation handler and add it to the animationFrame. If you ever want to cancel it, you might want to store it in a global variable though so you can call cancelAnimationFrame on it.
window.requestAnimationFrame(
makeMovingCircle( canvas, 15, 15, 10, 100, 2000 )
);
我根据记忆做了一个简单的例子:
const circle = $('#circle');
class Main {
constructor() {
}
start() {
requestAnimationFrame(this.loop.bind(this))
}
loop() {
const pos = circle.position();
// speed is a constant 1,1. But you could replace it by a variable
circle.css({top: pos.top+1, left: pos.left+1, position:'absolute'});
requestAnimationFrame(this.loop.bind(this))
}
}
const main = new Main();
main.start();
<div>
<img id="circle" src="https://playcode.io/static/img/logo.png"
alt="PlayCode logo">
<h1 id="msg"></h1>
</div>
#circle {
position: absolute;
top: 0;
left: 0;
}
在js中,对象通过引用传递,原始类型通过值传递。
我认为您应该避免在 moveCircle 函数中使用其他变量。那种函数通常称为"loop"或"gameLoop"或"update"。单击按钮时,向圆圈添加速度,而不创建新圆圈,例如 myCircle.speed = {x:2, y:2}
。在游戏循环中,将速度添加到每一帧的位置。
还要考虑增量时间,因为 requestAnimationFrame 会更快,具体取决于 PC/Mobile 和 运行 它。
如果愿意,您可以将应用程序包装到 Main class 上。 (就像我上面做的那样)
最后,如果你坚持给moveCircle
传递参数,你可以使用bind
。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
function c(a,b,c) {
console.log(a); console.log(b); console.log(c);
}
c.bind(window, 4,3)
// function c() // bind will return a function ! In a class usually the context that you want to pass is `this`. If you are playing directly in the console, the context is window by default (not that it matter in this example)
c.bind(window, 4,3)()
// 4
// 3
// undefined
对 moveCircle 的第一次调用(据我所知,它在按钮函数中)也应该是 requestAnimationFrame
,如 requestAnimationFrame(moveCircle.bind(window, other vars))
,您也可以在 moveCircle 中重复使用它,避免任何全局变量。不过,我建议制作一个 class 来包装您的应用程序,这样您就可以使用 class 的局部变量来保存游戏的当前状态,而不是让相同的状态仅存在于函数参数中.
我一直在尝试在不使用全局变量的情况下在 HTML Canvas 中制作一个 JavaScript 移动圆圈动画。我正在使用 requestAnimationFrame 函数。由于 JavaScript 不支持通过引用传递变量,我尝试创建一个 Circle class:
class Circle{
constructor(x, y, dx, dy) //set initial position and velocity of circle
{
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
}
function moveCircle(circle, other variables)
{
//clear canvas
//calculate new position and velocity using circle.x, etc.
//save new values to the object
//draw new circle into the canvas
requestAnimationFrame(moveCircle);
}
function button()//function called after click on button
{
//initial settings of canvas, intial condition
circle = new Circle(x, y, dx, dy);
moveCircle(circle, other vars);
}
这使一帧然后抛出错误"Cannot read property 'x' of undefined"。我究竟做错了什么?有没有其他方法可以做到这一点,同时避免使用全局变量?
首先,你不需要创建一个 class 你可以只传递对象中的坐标或作为单独的参数。
其次,您应该在 requestAnimationFrame
上使用 Function#bind
将相同的参数传递给下一个调用。
使用对象的示例:
function moveCircle(circle) {
console.log(circle.x);
if (circle.x) {
circle.x -= circle.dx;
requestAnimationFrame(moveCircle.bind(null, circle));
}
}
function button() {
moveCircle({
x: 500,
y: 0,
dx: 20,
dy: 0
});
}
button();
没有对象的例子:
function moveCircle(x, dx) {
console.log(x);
if (x) {
requestAnimationFrame(moveCircle.bind(null, x - dx, dx));
}
}
function button() {
moveCircle(500, 20);
}
button();
为简单起见,您可以使用 closure
创建一个内部处理程序,它知道您的圈子是什么样子以及它应该如何移动。闭包只是一个函数,它在本地定义自己的变量,然后 return 是一个可以访问这些变量的函数。
我们想要 return 一个只接受一个参数的函数:time
,因为这是浏览器传递给 AnimationFrame 中每个处理程序的参数。然后我们希望闭包函数绘制到全局定义的 canvas 中。看这里:
const canvas = document.body.appendChild( document.createElement( 'canvas' ) );
function makeMovingCircle( canvas, x, y, radius, distance, duration ){
// Lets get the context to draw here so we only need to fetch it once and store it, saving some computing time in favour of storing into memory.
const ctx = canvas.getContext( '2d' );
// We need to return a named function here so it can interally call itself again in requestAnimationFrame
return function AnimationHandler( time ){
// Lets just calculate an offset here based on the distance and duration we passed in above.
const progress = (time % duration / duration) * distance;
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.beginPath();
ctx.arc( x + progress, y, radius, 0, Math.PI*2, true );
ctx.stroke();
// Now call the named handler for the next animationFrame
window.requestAnimationFrame( AnimationHandler );
}
}
// Now lets make an animation handler and add it to the animationFrame. If you ever want to cancel it, you might want to store it in a global variable though so you can call cancelAnimationFrame on it.
window.requestAnimationFrame(
makeMovingCircle( canvas, 15, 15, 10, 100, 2000 )
);
我根据记忆做了一个简单的例子:
const circle = $('#circle');
class Main {
constructor() {
}
start() {
requestAnimationFrame(this.loop.bind(this))
}
loop() {
const pos = circle.position();
// speed is a constant 1,1. But you could replace it by a variable
circle.css({top: pos.top+1, left: pos.left+1, position:'absolute'});
requestAnimationFrame(this.loop.bind(this))
}
}
const main = new Main();
main.start();
<div>
<img id="circle" src="https://playcode.io/static/img/logo.png"
alt="PlayCode logo">
<h1 id="msg"></h1>
</div>
#circle {
position: absolute;
top: 0;
left: 0;
}
在js中,对象通过引用传递,原始类型通过值传递。
我认为您应该避免在 moveCircle 函数中使用其他变量。那种函数通常称为"loop"或"gameLoop"或"update"。单击按钮时,向圆圈添加速度,而不创建新圆圈,例如 myCircle.speed = {x:2, y:2}
。在游戏循环中,将速度添加到每一帧的位置。
还要考虑增量时间,因为 requestAnimationFrame 会更快,具体取决于 PC/Mobile 和 运行 它。
如果愿意,您可以将应用程序包装到 Main class 上。 (就像我上面做的那样)
最后,如果你坚持给moveCircle
传递参数,你可以使用bind
。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
function c(a,b,c) {
console.log(a); console.log(b); console.log(c);
}
c.bind(window, 4,3)
// function c() // bind will return a function ! In a class usually the context that you want to pass is `this`. If you are playing directly in the console, the context is window by default (not that it matter in this example)
c.bind(window, 4,3)()
// 4
// 3
// undefined
对 moveCircle 的第一次调用(据我所知,它在按钮函数中)也应该是 requestAnimationFrame
,如 requestAnimationFrame(moveCircle.bind(window, other vars))
,您也可以在 moveCircle 中重复使用它,避免任何全局变量。不过,我建议制作一个 class 来包装您的应用程序,这样您就可以使用 class 的局部变量来保存游戏的当前状态,而不是让相同的状态仅存在于函数参数中.