简体   繁体   中英

Improving performance of particles in canvas

I've been trying to recreate this Project in canvas and Javascript. I wasn't able to decipher the original code so I did it from scratch. The difference is that my projects starts to lag at about 2500 particles while the project above works with 30 000.

I'll paste my entire code below but these are the relevant parts:

var particleContainer = []
var distance = 10


for(let i = 0; i< square.height/distance; i++){
    for(let j = 0; j< square.height/distance; j++){
    particleContainer.push( new Particle(square.x + i*distance,square.y + j*distance) )
}  
}

if(  c < 90  ){
            i.xVelocity = a/c * -20
            i.yVelocity = b/c * -20
        }else if(90 < c && c < 95){
            i.xVelocity = a/c * -1
            i.yVelocity = b/c * -1
        }else if(c2 !== 0){
            i.xVelocity =( a2/c2 )
            i.yVelocity = (b2/c2 )
        }
  • (c -> distance between mouse and particle)

I'm creating a new Particle every 'distance' pixels of my square and pushing all of them into an array. when My mouse is to close to one of them the particle will start moving away from the mouse until it is 90-95px away from the Mouse.

30 000 pixels seems to work in a similar fashion judging from this line

  for ( i = 0; i < NUM_PARTICLES; i++ ) {

    p = Object.create( particle );
    p.x = p.ox = MARGIN + SPACING * ( i % COLS );
    p.y = p.oy = MARGIN + SPACING * Math.floor( i / COLS );

    list[i] = p;
  }

but that project doesn't run into the same case of performance Issues as I.

my full code for reference, (html is just a canvas):

var canvas = document.querySelector("canvas")
var c = canvas.getContext('2d')




function getMousePos(canvas, evt) {
    // var rect = canvas.getBoundingClientRect();
    return {
      x: evt.clientX,
      y: evt.clientY
    };
  }

  document.addEventListener('mousemove', function(evt) {
    var mousePos = getMousePos(canvas, evt);
    mouse.x= mousePos.x; 
    mouse.y= mousePos.y;
  }, false);

  var mouse = {
    x:0,
    y:0
  }

function Particle(x,y){
    this.x = x;
    this.y = y;
    this.xFixed = x;
    this.yFixed = y;
    this.radius = 1
    this.xVelocity = 0
    this.yVelocity = 0
    this.color = 'white'
}

Particle.prototype.draw = function(){
    c.save()
    c.beginPath()
    c.arc(this.x, this.y, this.radius,0,Math.PI*2,false)
    c.fillStyle = this.color
    c.fill()
}

Particle.prototype.update = function(){
    this.draw()
    this.x += this.xVelocity
    this.y += this.yVelocity
}

var square = {
    x: 500,
    y: 150,
    height: 500,
    width: 500,
    color: 'white'
}

var particleContainer = []
var distance = 10


for(let i = 0; i< square.height/distance; i++){
    for(let j = 0; j< square.height/distance; j++){
    particleContainer.push( new Particle(square.x + i*distance,square.y + j*distance) )
}

}





function animate(){
    requestAnimationFrame(animate);
    c.clearRect(0,0,window.innerWidth,window.innerHeight)

  canvas.width = window.innerWidth
canvas.height = window.innerHeight

    for(i of particleContainer){
        let a = mouse.x - i.x
        let b = mouse.y - i.y
        let c = Math.sqrt(Math.pow(b,2) + Math.pow(a,2))

        let a2 = i.xFixed - i.x
        let b2 = i.yFixed - i.y
        let c2 = Math.sqrt(Math.pow(b2,2) + Math.pow(a2,2))

        if(  c < 90  ){
            i.xVelocity = a/c * -20
            i.yVelocity = b/c * -20
        }else if(90 < c && c < 95){
            i.xVelocity = a/c * -1
            i.yVelocity = b/c * -1
        }else if(c2 !== 0){
            i.xVelocity =( a2/c2 )
            i.yVelocity = (b2/c2 )
        }


    }

   for(i of particleContainer){
       i.update()
   }
}

animate()

Learn to use the Performance tab in dev tools and you can see which functions are taking the most time. In this case I think you'll see that it's ctx.fill . The example you posted is writing pixels into an ImageData buffer which will be much faster than drawing and filling arcs. There are a lot of other small optimisations in the example but that's going to be the most important one, drawing is usually much slower than updating.

To get better rendering you need to add render objects to the same path. Once the path is created then you can draw them in one call to ctx.fill

Try to limit accessing innerWidth and innerHeight as they are very slow DOM objects that may cause reflows just by accessing them.

Further improvements can be made by using object pools and preallocation but that is beyond the scope of a single answer.

Make the following changes to your animate function.

var W = 1, H = 1;
function animate() {
    requestAnimationFrame(animate);
    c.clearRect(0 ,0, W, H)
    if (H !== innerHeight || W !== innerWidth) {
        W = canvas.width = innerWidth;
        H = canvas.height = innerHeight;
    }
    c.beginPath(); // start a new path
    c.fillStyle = "white";
    for (i of particleContainer) {  // update and draw all particles in one pass
        const a = mouse.x - i.x, b = mouse.y - i.y
        const c = (b * b + a * a) ** 0.5;
        const a2 = i.xFixed - i.x, b2 = i.yFixed - i.y
        const c2 = (b2 * b2 + a2 * a2) ** 0.5; 
        if(  c < 90  ){
            i.x += a/c * -20
            i.y += b/c * -20
        }else if(90 < c && c < 95){
            i.x += a/c * -1
            i.y += b/c * -1
        }else if(c2 !== 0){
            i.x +=( a2/c2 )
            i.y += (b2/c2 )
        }
        c.rect(i.x, i.y, 1,1);
    }
    c.fill();  // path complete render it.
   //for(i of particleContainer){  // no longer needed
   //    i.update()
   //}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM