Clifforus
Clifforus

Reputation: 113

HTML5 Canvas, Having Trouble Running Multiple Animations At Once

I have written code that takes two arrays, both of which contain co-ordinates for a four-cornered shape (effectively a start frame and an end frame), a canvas ID and a time value. The function then calculates dX and dY of each corner and uses window.performance.now() to create a timestamp. Then, on every requestAnimationFrame(), it calculates what the co-ordinates should be by using dX, dY, the old timestamp, a new timestamp and the time value from the function call. It looks like this:

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();
}

The goal is to have multiple animations of objects happening at once. I took the whole co-ordinate approach because I want the objects to appear as if they are coming from the horizon, creating a fake 3D perspective effect (all objects' starting frames would be a single point at the center of the canvas), and I do not want to warp the objects' textures.

Well, it works great for a single animation, but if I try to start a new animation on a completely different canvas while the first one is running, then the first animation stops dead in its tracks.

As you can see from my JS, I've tried getting around this with gratuitous use of this (I do not fully understand how this works yet, and every explanation I've read has left me even more confused), but it has not worked. I also tried a horrific approach which stored all the functions' own variables in one global array (the first time the function runs, all the variables are put in entries 1-30, the second time they're put in 31-60, etc). Unsurprisingly, that did not work either.

Here is a JSFiddle so you can see this scenario for yourself and play with my code. I am officially out of ideas. Any help will be greatly appreciated.

Upvotes: 1

Views: 375

Answers (1)

FallenNode
FallenNode

Reputation: 213

Like markE linked too, trying to call requestAnimationFrame multiple times won't work. Instead you make multiple objects and then call some sort of function on them each frame. I have created an example using your code: 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();
    }
}

Notes: Everytime you press the button a new object is added and simply overwrites previous ones for each frame, you should implement your program so that it checks if a specific animation has already started, you could also use the builtin mechanism to stop animation when complete (read the comments in the code)

You also need to change the on button click code

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

Lastly if you have further questions feel free to contact me

Upvotes: 1

Related Questions