如何将对象传递给 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传递参数,你可以使用bindhttps://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 的局部变量来保存游戏的当前状态,而不是让相同的状态仅存在于函数参数中.