Листья на ветру анимация с помощью метода requestAnimationFrame


Я создаю из листьев-ветер, эффект при этом ручки и возникли проблемы с производительностью, т. е. багги кадров каждый сейчас и потом на относительно современной машине. Видя, как производительность должна быть сильнее объекта requestAnimationFrame, мне любопытно о том, как мой код может быть дополнительно оптимизирован по производительности (и читаемость).

document.addEventListener('DOMContentLoaded', function() {
    const canvasEl = document.querySelector('#root-canvas');
    const canvasContext = canvasEl.getContext('2d');
    const canvasWidth = canvasEl.width;
    const canvasHeight = canvasEl.height;
    var leafArray=[];
    console.log(canvasEl.width);
    function Leaf (x,y, canvasContext) {
        this.x = x;
        this.y = y === 'default' ? canvasHeight*Math.random() : y;
        this.c = canvasContext;
        this.reverseDelay = 6000+Math.random()*9000;
        this.mainDelay = 2000+Math.random()*9000;
        this.waveSize = Math.random()*250;  // resp for initial spread
        // responsible for particle spread and leap to max speed
        this.factorA = 0.1;
        this.a = this.factorA;  // a is the sinus curve
        this.blowTimeout = 7000+Math.random()*2000;
        this.flakeW = 2;
        this.flakeH = this.flakeW;
        this.reverse = false;
        // resp for bouncing; higher value means more bouncing!
        // Will narrow particles down
        // also resp for sinking particles
        this.leafCeiling = -this.flakeH*2;
        this.dx = 0;
        this.dy = 0;
        this.draw = function() {
            this.c.fillRect(this.x, this.y, this.flakeW, this.flakeH);
            this.c.fillStyle = "green";
        };
        this.update = function() {
            // sinus wave code
            // if a is within desired sinus range
            if (this.a < Math.PI) {
                // a is an incrementing value for the sinus f
                this.a += Math.random()*this.factorA;
            }
            // if a is no longer within desired sinus range
            else if (this.a > Math.PI) {
                //console.log('sinus resetted');
                this.a = 0.1;
            }
            // sinus wave code end
            // account for canvas ceiling and ground
            if (this.y < this.leafCeiling) {
                this.dy = 2+Math.abs(Math.sin(2*this.a));
            }
            else if (this.y > canvasHeight-this.leafCeiling) {
                this.dy = (-6)*Math.abs(Math.sin(2*this.a))*Math.random();
            }
            // main delay; randomized so that each leaf appears at different t
            setTimeout(() => {
                // to slow down the loop
                setInterval(() => {
                    // as long as a is smaller than PI, sin(a) will be positive
                    // however, the highest f(a) is at sin(PI/2)
                    this.dy = Math.sin(2*this.a);
                    // the timeout prevents the leaves from looking like they were all triggered at the same time
                    // by, duh, triggering them with a randomized timeout
                    // if going right; on first run
                    if(this.reverse === false) {
                        this.dx = 1+Math.random()*4+Math.sin(2*this.a)*2;
                        // when reaching farthest right side
                        if(this.x > canvasWidth-this.flakeW*2){
                            // pos reset
                            // this is fine as this block is not looping (for long)
                            // timeout before going backwards
                            this.dx = 0;
                            this.x = canvasWidth+this.flakeW;
                            setTimeout(()=> {
                                this.reverse = true;
                            }, this.reverseDelay);
                        }
                    }
                    // if going left
                    else if (this.reverse === true) {
                        this.dx = -(1+Math.random()*4+Math.sin(2*this.a)*2);
                        // when reaching farthest left side
                        if(this.x < this.flakeW*(-1)){
                            // pos reset
                            this.x = this.flakeW*(-1);
                            // timeout before going forwards
                            // to recreate uniform trigger, we need to get rid of randomizations
                            this.dx = 0;
                            setTimeout(()=> {
                                this.reverse = false;
                            }, this.reverseDelay);
                        }
                    }
                }, this.waveSize); // end of main interval
            }, this.mainDelay); // end of main delay
            // these increments become problematic if update() is called multiple times
            this.x += this.dx;
            this.y += this.dy;
            this.draw();
        }; // end of update method
        this.diagnose = function () {
            setInterval(()=>{
                console.log('\t y is ' + Math.trunc(this.y) + '\t x is ' +Math.trunc(this.x));
            },2000);
        };
    } // end of Leaf class
    for (var i = 0; i<50; i++) {
        leafArray.push(new Leaf(-15, 'default', canvasContext));
    }
    function animate() {
        // looping
        canvasContext.clearRect(0,0, canvasWidth, canvasHeight); // clears the canvas
        for (var i = 0; i < leafArray.length; i++) {
            leafArray[i].update(); // update takes care of drawing the canvas
        }
        leafArray[0].diagnose();
        requestframeref = requestAnimationFrame(animate);
    } //end of animate
    animate();
})


Комментарии