如何在类对象中使用requestAnimationFrame

6
我有一个类,它带有一些坐标和持续时间的数据。我想使用它来动画显示一个svg。更明确地说,我想使用该数据在时间范围内更改svg属性。我在类外使用了step函数和requestAnimationFrame
function step(timestamp) {
  if (!start) start = timestamp
  var progress =  timestamp - start;
  var currentX = parseInt(document.querySelector('#start').getAttribute('cx'));
  var moveX =  distancePerFrame(circleMove.totalFrames(), circleMove.xLine);

  document.querySelector('#start').setAttribute('cx', currentX + moveX);
  if (progress < circleMove.duration) {
    window.requestAnimationFrame(step);
  }
}

var circleMove = new SingleLineAnimation(3000, startXY, endXY)

var start = null

function runProgram() {
  window.requestAnimationFrame(step);
}

我可以将其转换为一个方法,将circleLine替换为this。这在第一次运行时正常工作,但当它第二次调用this.step回调时,我们就陷入了回调黑洞,对this的引用被破坏了。使用旧的self = this也不起作用,一旦我们跳进回调函数,this就变成未定义的(我不确定为什么)。以下是其作为一个方法的代码:

step(timestamp) {
  var self = this;

  if (!start) start = timestamp
  var progress =  timestamp - start;
  var currentX = parseInt(document.querySelector('#start').getAttribute('cx'));
  var moveX =  distancePerFrame(self.totalFrames(), self.xLine);

  document.querySelector('#start').setAttribute('cx', currentX + moveX);
  if (progress < self.duration) {
    window.requestAnimationFrame(self.step);
  }
}

有没有关于如何在对象内部保持“连线”的想法?

这是一段代码,大致上可以使用在类外定义的step函数。

class SingleLineAnimation { 
  constructor(duration, startXY, endXY) {
    this.duration = duration;
    this.xLine = [ startXY[0], endXY[0] ];
    this.yLine = [ startXY[1], endXY[1] ];
  }

  totalFrames(framerate = 60) { // Default to 60htz ie, 60 frames per second
    return Math.floor(this.duration * framerate / 1000);
  } 

  frame(progress) {
    return this.totalFrames() - Math.floor((this.duration - progress) / 17 );
  } 
}

这段内容也将被插入到类中,现在它只是一个辅助函数:
function distancePerFrame(totalFrames, startEndPoints) {
  return totalFrames > 0 ? Math.floor(Math.abs(startEndPoints[0] - startEndPoints[1]) / totalFrames) : 0;
}

点击按钮来...

function runProgram() {
  window.requestAnimationFrame(step);
}

runProgram() 函数在何时何地被调用?SingleLineAnimation 是如何定义的,它定义在哪里? - zer00ne
distancePerFrame() 怎么样? - zer00ne
我刚刚意识到的是,我可以将 requestAnimationFrame 定义为对象上的一个属性。 - icicleking
1
@icicleking 如果你丢失了你的 this,那是没有帮助的。幸运的是,这不是破解这个难题的唯一方法。 - Alnitak
1个回答

10

您需要将requestAnimationFrame回调函数绑定到上下文。这样做的标准方式如下:

window.requestAnimationFrame(this.step.bind(this))

但这并不理想,因为你需要一遍又一遍地调用 .bind 并创建一个新的函数引用,每帧一次。

如果你有一个本地作用域变量设置为 this.step.bind(this),你可以传递它并避免持续重新绑定。

另一种选择是这样的:

function animate() {

    var start = performance.now();
    el = document.querySelector('#start');

    // use var self = this if you need to refer to `this` inside `frame()`

    function frame(timestamp) {

        var progress =  timestamp - start;
        var currentX = parseInt(el.getAttribute('cx'));
        var moveX =  distancePerFrame(circleMove.totalFrames(), circleMove.xLine);
        el.setAttribute('cx', currentX + moveX);

        if (progress < circleMove.duration) {
            window.requestAnimationFrame(frame);
        }
    }

    window.requestAnimationFrame(frame);
}

也就是说,您正在设置初始状态,然后在一个纯本地作用域函数内执行动画,该函数由requestAnimationFrame伪递归调用。

NB:如果您不小心同时调用另一个启动动画的函数,则代码的任何版本都会产生交互问题。


你说得对,每次绑定似乎是一种(或者说是唯一的)将连线放在对象内部的方法,但这对性能来说是不好的。在单独的函数中执行计时可能是更好的选择。 - icicleking

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接