【问题标题】:HTML5 Canvas, Having Trouble Running Multiple Animations At OnceHTML5 Canvas,一次运行多个动画时遇到问题
【发布时间】:2016-08-10 12:20:13
【问题描述】:

我编写了包含两个数组的代码,这两个数组都包含一个四角形状的坐标(实际上是一个起始帧和一个结束帧)、一个画布 ID 和一个时间值。然后该函数计算每个角的 dX 和 dY 并使用 window.performance.now() 创建时间戳。然后,在每个requestAnimationFrame() 上,它通过使用 dX、dY、旧时间戳、新时间戳和函数调用的时间值来计算坐标应该是什么。它看起来像这样:

function doAnim(cv, startFrame, endFrame, animTime)
{   
    this.canvas = document.getElementById(cv);
    this.ctx = this.canvas.getContext('2d');

    if(startFrame.length != endFrame.length)
    {
        return('Error: Keyframe arrays do not match in length');
    };


    this.animChange = new Array();

    for(i=1;i<=startFrame.length;i++)
    {
        var a = startFrame[i];
        var b = endFrame[i]
        var c = b - a;
        this.animChange[i] = c;
    }
    this.timerStart = window.performance.now();

    function draw()
    {       
        this.requestAnimationFrame(draw, cv);
        this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
        this.currentFrame = new Array();
        for(i=1;i<=startFrame.length;i++)
        {
            this.currentFrame[i] = startFrame[i]+(this.animChange[i]*((window.performance.now()-this.timerStart)/animTime));
        }
        if((window.performance.now()-this.timerStart)>=animTime)
        {
            this.ctx.beginPath()
            this.ctx.moveTo(endFrame[1], endFrame[2]);
            this.ctx.lineTo(endFrame[3], endFrame[4]);
            this.ctx.lineTo(endFrame[5], endFrame[6]);
            this.ctx.lineTo(endFrame[7], endFrame[8]);
            this.ctx.fill();
            return;
        }
        else
        {
            this.ctx.beginPath()
            this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
            this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
            this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
            this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
            this.ctx.fill();
        }
    }
    draw();
}

目标是同时发生多个对象动画。我采用了整个坐标的方法,因为我希望对象看起来好像它们来自地平线,创建一个假的 3D 透视效果(所有对象的起始帧都是画布中心的一个点),并且我不想扭曲对象的纹理。

嗯,它适用于单个动画,但如果我尝试在第一个动画运行时在完全不同的画布上开始一个新动画,那么第一个动画将停止在其轨道上。

正如你从我的 JS 中看到的那样,我尝试通过无偿使用 this 来解决这个问题(我还不完全理解 this 的工作原理,我读过的每一个解释都让我更加难忘困惑),但它没有奏效。我还尝试了一种可怕的方法,将所有函数自己的变量存储在一个全局数组中(函数第一次运行时,所有变量都放在条目 1-30 中,第二次它们放在 31-60 中,等等)。不出所料,这也不起作用。

Here is a JSFiddle so you can see this scenario for yourself and play with my code.我正式没有想法了。任何帮助将不胜感激。

【问题讨论】:

  • 这个元素在哪里 document.getElementById(cv);
  • cv 是画布的 ID,在 doAnim 函数的第一个参数中定义。我将在我的测试设置中运行的函数将是 doAnim('canvas1', anim_frame1, anim_frame2, 1000) 在 ID 为“canvas1”的画布中执行 1 秒动画。
  • 查看之前的Q&A,它展示了如何在单个 requestAnimationFrame 循环下运行多个动画。

标签: javascript html animation canvas


【解决方案1】:

就像 markE 也链接一样,尝试多次调用 requestAnimationFrame 是行不通的。 相反,您制作多个对象,然后在每一帧对它们调用某种函数。 我使用您的代码创建了一个示例: https://jsfiddle.net/samcarlin/2bxn1r79/7/

var anim0frame1 = new Array();
anim0frame1[1] = 0;
anim0frame1[2] = 0;
anim0frame1[3] = 50;
anim0frame1[4] = 0;
anim0frame1[5] = 50;
anim0frame1[6] = 150;
anim0frame1[7] = 0;
anim0frame1[8] = 150;

var anim0frame2 = new Array();
anim0frame2[1] = 200;
anim0frame2[2] = 200;
anim0frame2[3] = 300;
anim0frame2[4] = 250;
anim0frame2[5] = 300;
anim0frame2[6] = 300;
anim0frame2[7] = 200;
anim0frame2[8] = 250;


//Call global 
animations = [];
requestAnimationFrame( GlobalStep );

function GlobalStep(delta){
    //Functions called by request animation frame have the new time as an argument
  //so delta should be approximately the same as window.performance.now()
  //especially in realtime applications, which this is

  //Check if we have any animation objects
  if(animations.length > 0){
    //Iterate through and call draw on all animations
    for(var i=0; i<animations.length; i++){
      if(animations[i].draw(delta)){
        //Basically we have it so if the draw function returns true we stop animating the object
        //And remove it from the array, so have the draw function return true when animation is complete
        animations[i].splice(i, 0);
        //We removed an object from the array, so we decrement i
        i--;
      }
    }
  }

  //And of course call requestAnimationFrame
  requestAnimationFrame( GlobalStep );
}

function AnimationObject(cv, startFrame, endFrame, animTime){

    //Add this object to the objects arrays
  animations.push(this);

  //We need to store start and end frame
  this.startFrame = startFrame;
  this.endFrame = endFrame;
  this.animTime = animTime;

  //Your code
    this.canvas = document.getElementById(cv);
  this.ctx = this.canvas.getContext('2d');

  if (startFrame.length != endFrame.length) {
    return ('Error: Keyframe arrays do not match in length');
  };

  this.animChange = new Array();

  for (i = 1; i <= startFrame.length; i++) {
    var a = startFrame[i];
    var b = endFrame[i]
    var c = b - a;
    this.animChange[i] = c;
  }

  this.timerStart = window.performance.now();
}

//This adds a function to an object, but in such a way that every object shares the same function
//Imagine a kitchen, each object is a person, and this function is a spoon
//by defining this function in this manner Object.prototype.function_name = function(arguments){}
//We make it so one function definition is needed, essentially allowing all the people to share one spoon,
//the 'this' variable still refers to whichever object we call this method, and we save memory etc.
AnimationObject.prototype.draw = function(newTime){
    //I added this to start frame so we get what we stored earlier
     this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
   this.currentFrame = new Array();

    for (i = 1; i <= this.startFrame.length; i++) {
      this.currentFrame[i] = this.startFrame[i] + (this.animChange[i] * ((newTime - this.timerStart) / this.animTime));
    }
    if ((newTime - this.timerStart) >= this.animTime) {
      this.ctx.beginPath()
      this.ctx.moveTo(this.endFrame[1], this.endFrame[2]);
      this.ctx.lineTo(this.endFrame[3], this.endFrame[4]);
      this.ctx.lineTo(this.endFrame[5], this.endFrame[6]);
      this.ctx.lineTo(this.endFrame[7], this.endFrame[8]);
      this.ctx.fill();
      return;
    } else {
      this.ctx.beginPath()
      this.ctx.moveTo(this.currentFrame[1], this.currentFrame[2]);
      this.ctx.lineTo(this.currentFrame[3], this.currentFrame[4]);
      this.ctx.lineTo(this.currentFrame[5], this.currentFrame[6]);
      this.ctx.lineTo(this.currentFrame[7], this.currentFrame[8]);
      this.ctx.fill();
    }
}

注意事项: 每次按下按钮时,都会添加一个新对象并简单地覆盖每一帧之前的对象,您应该实现您的程序,以便它检查特定动画是否已经开始,您还可以使用内置机制在完成时停止动画(阅读代码中的cmets)

您还需要更改 on 按钮的点击代码

 <button onclick="new AnimationObject('canvas1', anim0frame1, anim0frame2, 3000);">

最后,如果您还有其他问题,请随时与我联系

【讨论】:

  • 哇,多么简洁而有帮助的回复,谢谢!我特别喜欢你的cmets。我还没有很好地处理你的代码,但我会说我试图用这些动画实现的本质上是一个以特定方式开始和停止并且不能被用户打断的大动画,所以我想一旦所有单独的动画都正确组合在一起,实施您的解决方案应该是相当轻松的,因为不需要即时添加新的动画。考虑到这一点,您认为还有什么可以做的来精简代码吗?
猜你喜欢
  • 2012-10-26
  • 2022-12-18
  • 1970-01-01
  • 1970-01-01
  • 2011-09-18
  • 2015-08-12
  • 2011-12-25
  • 2019-09-12
  • 1970-01-01
相关资源
最近更新 更多