javascriptanimationgraphicssynchronizationrequestanimationframe

requestAnimationFrame() animation of multiple objects sync problem


The task is to animate number of falling rectangles simulating rain,enter image description here when rectangle – jet touches ground -draw star. To impelemnt it there are 3 classes: GitHub repository.

// Shapes:
class WaterJet   {  //  falling rectangles
class RandomStar {  //  stars

    //Each shape has
    isAlive = true;  // variable defines if shape still need o be rendered
    draw(delta_ms)   // method to draw shape obaining time from last draw

and engine to animate all of the shapes collected in array animatedItems with requestAnimationFrame:

class AnimateBatch {
    constructor() {
        this.animatedItems = []; // Container to store entities to draw  )
        this.delta_ms = 0;                  // Delta between draw() methods run
    }
    //Method to animate batch of shapes
     #animate(previousTime) {
           
         const currentTime = performance.now();  
         this.delta_ms = previousTime ? currentTime - previousTime : 0;  
            
         // Draw and filter only "alive" shapes
         this.animatedItems = this.animatedItems.filter((item) => {
             item.draw(this.delta_ms);
             return item.isAlive; // leave only shapes with isAlive=true
         });
        
         if (this.animatedItems.length > 0) {
             this.requestId = requestAnimationFrame((newTime) => this.#animate(newTime));
    

Speed of shapes animation is caculated in draw() method  on the base of delta_ms (time left from last draw) ,

I need to catch moment when one shape is expired and on this event add new shape to AnimateBatch.animatedItems:

    function addStar(){
        animateBatch.add(newStar);
    }

here comes trouble, when this shape added by listener of the button it is ok:

buttonDo.addEventListener('click', function() {
    addStar();
});

Cyclic printot of content of rendered shapes show that stars added and atay in array:

0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
2. Anisothermal.html:250 ______________star added RandomStar-1
0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
2. RandomStar-1 :  true   <<----added
0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
2. RandomStar-1 :  true    <<---- and remains

But when the same function implemnted from listener:

jets[i].OnJetOutOfBorder( () => {
    addStar();
});

In listener of the shape:

class RandomStar {
    constructor(ctx, centerX, centerY, spikeLength = 2) {
        this.OnStarGone = null;    
    }
    draw(delta_ms) {
        if(!this.isAlive) {   // When shape already finish life: execute listener 
            // Implement litener when star is expired
            if(this.OnStarGone)this.OnStarGone();
            return;
        }
    addOnStarGone(callback){
        this.OnStarGone = callback;
    }

Than shape added to array but disappears from it :

0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
2. Anisothermal.html:228 _____________________________listener________
0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
2. RandomStar-5 :  true   <<-- it is added 
2. Anisothermal.html:250 ______________star added RandomStar-5
0. AnimatedItem-1 :  true
1. WaterJet-1 :  true
0. AnimatedItem-1 :  true    <<---- And not in array any more
1. WaterJet-1 :  true
0. AnimatedItem-1 :  true
1. WaterJet-1 :  true

The result is major letdown and I doubt if my approach to animate is right? Please help to understand such wierd bihavour

  1. How possible to modify list of rendering shapes during the animation?

  2. Why despite calculation of the speeds done on the base of time periods the animation goes with jerks, espatially sharp and intrrupting when a user intracts with interface (it has some other controllers to modify animation in fly). At the same time: calculation based on time difference between executing requestAnimationFrame() gives huge error.

  3. I see that requestAnimationFrame() provides relatively hight FPS 16-17ms per frame, is it possble to set it lower to sacrifice update frequency in the sake of the performance?

Build seamless animation with possibility to add and remove new shapes to render list while animation.


Solution

  • With respect to 2 on your list of questions: The way you are doing time is wrong. requestAnimationFrame() passes the current time into the callback function, not the previous time. You call it newTime in one place, but then call previousTime and use it as a previous time in your callback. So...

    Add a previousTime property in your constructor

    this.previousTime = performance.now()
    

    Change the parameter name in your callback to currentTime

    animate(currentTime) {
    

    Finally, change your delta_ms calculation appropriately

    this.delta_ms = currentTime - this.previousTime
    this.previousTime = currentTime