最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Canvas particles, collisions and performance - Stack Overflow

programmeradmin1浏览0评论

I'm creating a web app which has an interactive background with particles bouncing around. At all times there are about 200 circular particles on the screen and at most around 800 particles. Some of the collisions and effects that are being run for the particles are the following prototypes. I wonder if I could improve the performance by using web workers to do these calculations?

/**
*   Particles
*/

Jarvis.prototype.genForegroundParticles = function(options, count){

    count = count || this.logoParticlesNum;

    for (var i = 0; i < count; i++) {
        this.logoParticles.push(new Particle());
    }

}

Jarvis.prototype.genBackgroundParticles = function(options, count){

    count = count || this.backgroundParticlesNum;

    for (var i = 0; i < count; i++) {
        this.backgroundParticles.push(new Particle(options));
    }

}

Jarvis.prototype.motion = {
    linear : function(particle, pIndex, particles){
        particle.x += particle.vx
        particle.y += particle.vy
    },
    normalizeVelocity : function(particle, pIndex, particles){

        if (particle.vx - particle.vxInitial > 1) {
            particle.vx -= 0.05;
        } else if (particle.vx - particle.vxInitial < -1) {
            particle.vx += 0.05;
        }

        if (particle.vy - particle.vyInitial > 1) {
            particle.vy -= 0.05;
        } else if (particle.vx - particle.vxInitial < -1) {
            particle.vy += 0.05;
        }

    },
    explode : function(particle, pIndex, particles) {

        if (particle.isBottomOut()) {
            particles.splice(pIndex, 1);
        } else {
            particle.x += particle.vx;
            particle.y += particle.vy;
            particle.vy += 0.1;
        }

        if (particles.length === 0){
            particles.motion.removeMotion("explode");
            this.allowMenu = true;
        }       

    }
}

Jarvis.prototype.collision = {
    boundingBox: function(particle, pIndex, particles){

        if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) {
            particle.vy *= -1;
        }

        if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) {
            particle.vx *= -1;
        }
    },
    boundingBoxGravity: function(particle, pIndex, particles){
        // TODO: FIX GRAVITY TO WORK PROPERLY IN COMBINATION WITH FX AND MOTION
        if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) {
            particle.vy *= -1;
            particle.vy += 5;
        } 

        if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) {
            particle.vx *= -1;
            particle.vx += 5;
        }

    },
    infinity: function(particle, pIndex, particles){

        if (particle.x > this.WIDTH){
            particle.x = 0;
        }

        if (particle.x < 0){
            particle.x = this.WIDTH;
        }

        if (particle.y > this.HEIGHT){
            particle.y = 0;
        }       

        if (particle.y < 0) {
            particle.y = this.HEIGHT;
        }

    }
}

Jarvis.prototype.fx = {
    link : function(particle, pIndex, particles){

        for(var j = pIndex + 1; j < particles.length; j++) {

            var p1 = particle;
            var p2 = particles[j];
            var particleDistance = getDistance(p1, p2);

            if (particleDistance <= this.particleMinLinkDistance) {
                this.backgroundCtx.beginPath();
                this.backgroundCtx.strokeStyle = "rgba("+p1.red+", "+p1.green+", "+p1.blue+","+ (p1.opacity - particleDistance / this.particleMinLinkDistance) +")";
                this.backgroundCtx.moveTo(p1.x, p1.y);
                this.backgroundCtx.lineTo(p2.x, p2.y);
                this.backgroundCtx.stroke();
                this.backgroundCtx.closePath();
            }
        }
    },
    shake : function(particle, pIndex, particles){

        if (particle.xInitial - particle.x >= this.shakeAreaThreshold){
            particle.xOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.WIDTH);
        } else if (particle.xInitial - particle.x <= -this.shakeAreaThreshold) {
            particle.xOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.WIDTH);
        }

        if (particle.yInitial - particle.y >= this.shakeAreaThreshold){
            particle.yOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.HEIGHT);
        } else if (particle.yInitial - particle.y <= -this.shakeAreaThreshold) {
            particle.yOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.HEIGHT);
        }       

        particle.x += particle.xOper;
        particle.y += particle.yOper;

    },
    radialWave : function(particle, pIndex, particles){

        var distance = getDistance(particle, this.center);

        if (particle.radius >= (this.dim * 0.0085)) {
            particle.radiusOper = -0.02;
        } else if (particle.radius <= 1) {
            particle.radiusOper = 0.02;
        }

        particle.radius += particle.radiusOper * particle.radius;
    },
    responsive : function(particle, pIndex, particles){

        var newPosX = (this.logoParticles.logoOffsetX + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.x;
        var newPosY = (this.logoParticles.logoOffsetY + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.y;

        if (particle.xInitial !== newPosX || particle.yInitial !== newPosY){

            particle.xInitial = newPosX;
            particle.yInitial = newPosY;
            particle.x = particle.xInitial;
            particle.y = particle.yInitial;

        }

    },
    motionDetect : function(particle, pIndex, particles){

        var isClose = false;
        var distance = null;

        for (var i = 0; i < this.touches.length; i++) {

            var t = this.touches[i];

            var point = {
                x : t.clientX,
                y : t.clientY
            }

            var d = getDistance(point, particle); 

            if (d <= this.blackhole) {
                isClose = true;

                if (d <= distance || distance === null) {
                    distance = d;
                }

            }  

        }

        if (isClose){
            if (particle.radius < (this.dim * 0.0085)) {
                particle.radius += 0.25;
            }
            if (particle.green >= 0 && particle.blue >= 0) {
                particle.green -= 10;
                particle.blue -= 10;
            }           
        } else {
            if (particle.radius > particle.initialRadius) {
                particle.radius -= 0.25;
            }
            if (particle.green <= 255 && particle.blue <= 255) {
                particle.green += 10;
                particle.blue += 10;
            }           
        }

    },
    reverseBlackhole : function(particle, pIndex, particles){

        for (var i = 0; i < this.touches.length; i++) {

            var t = this.touches[i];

            var point = {
                x : t.clientX,
                y : t.clientY
            } 

            var distance = getDistance(point, particle);

            if (distance <= this.blackhole){

                var diff = getPointsDifference(point, particle);

                particle.vx += -diff.x / distance;
                particle.vy += -diff.y / distance;
            }

        }
    }
}

Furthermore in case anyone wonders I have 3 canvas layers & I'll add the particles rendering function and the clear function for all canvas layers

  1. Background which draws a full screen radial gradient & particles

  2. Menu canvas

  3. Menu button overlay selectors (show which menu is active etc)


Jarvis.prototype.backgroundDraw = function() {

    // particles

    var that = this;

    this.logoParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.logoParticles.motion.forEach(function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
        that.logoParticles.fx.forEach(function(fxType, fxIndex){
            that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
        that.logoParticles.collision.forEach(function(collisionType, collisionIndex){
            that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
    });

    this.backgroundParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.backgroundParticles.motion.forEach(function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
        that.backgroundParticles.fx.forEach(function(fxType, fxIndex){
            that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
        that.backgroundParticles.collision.forEach(function(collisionType, collisionIndex){
            that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
    });

}

Jarvis.prototype.clearCanvas = function() {

    switch(this.background.type){
        case "radial_gradient":
            this.setBackgroundRadialGradient(this.background.color1, this.background.color2);
            break;
        case "plane_color":
            this.setBackgroundColor(this.background.red, this.background.green, this.background.blue, this.background.opacity);
            break;
        default:
            this.setBackgroundColor(142, 214, 255, 1);
    }

    this.foregroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance);
    this.middlegroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance);
}

Jarvis.prototype.mainLoop = function() {
    this.clearCanvas();
    this.backgroundDraw();
    this.drawMenu();
    window.requestAnimFrame(this.mainLoop.bind(this));
}

Any other optimization tips will be greatly appreciated. I've read a couple of articles but I'm not sure how to optimize this code further.

I'm creating a web app which has an interactive background with particles bouncing around. At all times there are about 200 circular particles on the screen and at most around 800 particles. Some of the collisions and effects that are being run for the particles are the following prototypes. I wonder if I could improve the performance by using web workers to do these calculations?

/**
*   Particles
*/

Jarvis.prototype.genForegroundParticles = function(options, count){

    count = count || this.logoParticlesNum;

    for (var i = 0; i < count; i++) {
        this.logoParticles.push(new Particle());
    }

}

Jarvis.prototype.genBackgroundParticles = function(options, count){

    count = count || this.backgroundParticlesNum;

    for (var i = 0; i < count; i++) {
        this.backgroundParticles.push(new Particle(options));
    }

}

Jarvis.prototype.motion = {
    linear : function(particle, pIndex, particles){
        particle.x += particle.vx
        particle.y += particle.vy
    },
    normalizeVelocity : function(particle, pIndex, particles){

        if (particle.vx - particle.vxInitial > 1) {
            particle.vx -= 0.05;
        } else if (particle.vx - particle.vxInitial < -1) {
            particle.vx += 0.05;
        }

        if (particle.vy - particle.vyInitial > 1) {
            particle.vy -= 0.05;
        } else if (particle.vx - particle.vxInitial < -1) {
            particle.vy += 0.05;
        }

    },
    explode : function(particle, pIndex, particles) {

        if (particle.isBottomOut()) {
            particles.splice(pIndex, 1);
        } else {
            particle.x += particle.vx;
            particle.y += particle.vy;
            particle.vy += 0.1;
        }

        if (particles.length === 0){
            particles.motion.removeMotion("explode");
            this.allowMenu = true;
        }       

    }
}

Jarvis.prototype.collision = {
    boundingBox: function(particle, pIndex, particles){

        if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) {
            particle.vy *= -1;
        }

        if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) {
            particle.vx *= -1;
        }
    },
    boundingBoxGravity: function(particle, pIndex, particles){
        // TODO: FIX GRAVITY TO WORK PROPERLY IN COMBINATION WITH FX AND MOTION
        if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) {
            particle.vy *= -1;
            particle.vy += 5;
        } 

        if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) {
            particle.vx *= -1;
            particle.vx += 5;
        }

    },
    infinity: function(particle, pIndex, particles){

        if (particle.x > this.WIDTH){
            particle.x = 0;
        }

        if (particle.x < 0){
            particle.x = this.WIDTH;
        }

        if (particle.y > this.HEIGHT){
            particle.y = 0;
        }       

        if (particle.y < 0) {
            particle.y = this.HEIGHT;
        }

    }
}

Jarvis.prototype.fx = {
    link : function(particle, pIndex, particles){

        for(var j = pIndex + 1; j < particles.length; j++) {

            var p1 = particle;
            var p2 = particles[j];
            var particleDistance = getDistance(p1, p2);

            if (particleDistance <= this.particleMinLinkDistance) {
                this.backgroundCtx.beginPath();
                this.backgroundCtx.strokeStyle = "rgba("+p1.red+", "+p1.green+", "+p1.blue+","+ (p1.opacity - particleDistance / this.particleMinLinkDistance) +")";
                this.backgroundCtx.moveTo(p1.x, p1.y);
                this.backgroundCtx.lineTo(p2.x, p2.y);
                this.backgroundCtx.stroke();
                this.backgroundCtx.closePath();
            }
        }
    },
    shake : function(particle, pIndex, particles){

        if (particle.xInitial - particle.x >= this.shakeAreaThreshold){
            particle.xOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.WIDTH);
        } else if (particle.xInitial - particle.x <= -this.shakeAreaThreshold) {
            particle.xOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.WIDTH);
        }

        if (particle.yInitial - particle.y >= this.shakeAreaThreshold){
            particle.yOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.HEIGHT);
        } else if (particle.yInitial - particle.y <= -this.shakeAreaThreshold) {
            particle.yOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.HEIGHT);
        }       

        particle.x += particle.xOper;
        particle.y += particle.yOper;

    },
    radialWave : function(particle, pIndex, particles){

        var distance = getDistance(particle, this.center);

        if (particle.radius >= (this.dim * 0.0085)) {
            particle.radiusOper = -0.02;
        } else if (particle.radius <= 1) {
            particle.radiusOper = 0.02;
        }

        particle.radius += particle.radiusOper * particle.radius;
    },
    responsive : function(particle, pIndex, particles){

        var newPosX = (this.logoParticles.logoOffsetX + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.x;
        var newPosY = (this.logoParticles.logoOffsetY + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.y;

        if (particle.xInitial !== newPosX || particle.yInitial !== newPosY){

            particle.xInitial = newPosX;
            particle.yInitial = newPosY;
            particle.x = particle.xInitial;
            particle.y = particle.yInitial;

        }

    },
    motionDetect : function(particle, pIndex, particles){

        var isClose = false;
        var distance = null;

        for (var i = 0; i < this.touches.length; i++) {

            var t = this.touches[i];

            var point = {
                x : t.clientX,
                y : t.clientY
            }

            var d = getDistance(point, particle); 

            if (d <= this.blackhole) {
                isClose = true;

                if (d <= distance || distance === null) {
                    distance = d;
                }

            }  

        }

        if (isClose){
            if (particle.radius < (this.dim * 0.0085)) {
                particle.radius += 0.25;
            }
            if (particle.green >= 0 && particle.blue >= 0) {
                particle.green -= 10;
                particle.blue -= 10;
            }           
        } else {
            if (particle.radius > particle.initialRadius) {
                particle.radius -= 0.25;
            }
            if (particle.green <= 255 && particle.blue <= 255) {
                particle.green += 10;
                particle.blue += 10;
            }           
        }

    },
    reverseBlackhole : function(particle, pIndex, particles){

        for (var i = 0; i < this.touches.length; i++) {

            var t = this.touches[i];

            var point = {
                x : t.clientX,
                y : t.clientY
            } 

            var distance = getDistance(point, particle);

            if (distance <= this.blackhole){

                var diff = getPointsDifference(point, particle);

                particle.vx += -diff.x / distance;
                particle.vy += -diff.y / distance;
            }

        }
    }
}

Furthermore in case anyone wonders I have 3 canvas layers & I'll add the particles rendering function and the clear function for all canvas layers

  1. Background which draws a full screen radial gradient & particles

  2. Menu canvas

  3. Menu button overlay selectors (show which menu is active etc)


Jarvis.prototype.backgroundDraw = function() {

    // particles

    var that = this;

    this.logoParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.logoParticles.motion.forEach(function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
        that.logoParticles.fx.forEach(function(fxType, fxIndex){
            that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
        that.logoParticles.collision.forEach(function(collisionType, collisionIndex){
            that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
        });
    });

    this.backgroundParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.backgroundParticles.motion.forEach(function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
        that.backgroundParticles.fx.forEach(function(fxType, fxIndex){
            that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
        that.backgroundParticles.collision.forEach(function(collisionType, collisionIndex){
            that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
        });
    });

}

Jarvis.prototype.clearCanvas = function() {

    switch(this.background.type){
        case "radial_gradient":
            this.setBackgroundRadialGradient(this.background.color1, this.background.color2);
            break;
        case "plane_color":
            this.setBackgroundColor(this.background.red, this.background.green, this.background.blue, this.background.opacity);
            break;
        default:
            this.setBackgroundColor(142, 214, 255, 1);
    }

    this.foregroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance);
    this.middlegroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance);
}

Jarvis.prototype.mainLoop = function() {
    this.clearCanvas();
    this.backgroundDraw();
    this.drawMenu();
    window.requestAnimFrame(this.mainLoop.bind(this));
}

Any other optimization tips will be greatly appreciated. I've read a couple of articles but I'm not sure how to optimize this code further.

Share Improve this question edited Nov 6, 2014 at 8:43 Peter O. 32.9k14 gold badges85 silver badges97 bronze badges asked Nov 1, 2014 at 12:50 0x_Anakin0x_Anakin 3,2795 gold badges50 silver badges90 bronze badges 4
  • Have you investigated WebGL as a platform to optimize your particles? – markE Commented Nov 1, 2014 at 18:14
  • No to be honest and the reason I did not is because I'm not sure about the webGL support in devices other than laptops and desktops – 0x_Anakin Commented Nov 1, 2014 at 19:42
  • You mention you particle system is "interactive". Does that include influences by either the user or by code--if so how? Or is your particle system pletely autonomous--where the particles only react through collisions with other particles? :-) – markE Commented Nov 6, 2014 at 20:09
  • I have added event listeners on mousemove and touchmove, which does an explosion effect on the particles. furthermore thnx for the answers so far I will accept the best answer soon. – 0x_Anakin Commented Nov 6, 2014 at 23:13
Add a ment  | 

4 Answers 4

Reset to default 2

You can use FabricJS Canvas Library. FabricJS by default supports interactivity, when you create a new object (circle, rectangle and etc) you can manipulate it by mouse or touchscreen.

var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
    width: 10, height: 20,
    left: 100, top: 100,
    fill: 'yellow',
    angle: 30
});

canvas.add(rect); 

See, we work there in object oriented way.

If you are looking to speed up code, here are some micro-optimizations:

  • for(var i = 0, l = bla.length; i < l; i++) { ... } instead of bla.forEach(...)
  • reduce callback usage. Inline simple stuff.
  • paring against distance is slow because of the SQRT. radius <= distance is slow, radius*radius <= distanceSquared is fast.
  • calculating the distance is done by calculating the difference. You now do 2 function calls, first to get the distance, then to get the difference. here's a small rewrite: No function calls, No unnecessary calculations.
reverseBlackhole : function(particle, pIndex, particles)
{
    var blackholeSqr = this.blackhole * this.blackhole,
        touches = this.touches,
        fnSqrt = Math.sqrt,
        t, diffX, diffY, dstSqr;
    for (var i = 0, l = touches.length; i < l; i++) {
        t = touches[i];
        diffX = particle.x - t.clientX;
        diffY = particle.y - t.clientY;
        distSqr = (diffX * diffX + diffY * diffY);
        // paring distance without a SQRT needed
        if (dstSqr <= blackholeSqr){
            var dist = Math.sqrt(dstSqr); 
            particle.vx -= diffX / dist;
            particle.vy -= diffY / dist;
        }
    }
}

To speed up drawing (or make it lag less during drawing):

  • Separate your calculations from your drawing
  • Only request a redraw after you have updated your calculations

And for the whole animation:

  • this.backgroundParticles.forEach(..): in case of 200 particles, this will do
  • 200 particles times ( this.backgroundParticles.forEach( )
    • 200 particles ( that.backgroundParticles.motion.forEach )
    • 200 particles ( that.backgroundParticles.fx.forEach )
    • 200 particles ( that.backgroundParticles.collision.forEach )
  • same goes for this.foregroundparticles.forEach(..)
  • let's say we have 200 background and 100 foreground, that is (2002003) + (1001003) callbacks, which is 150000 callbacks, per tick. And we haven't actually calculated a single thing yet, haven't displayed anything either.
  • Run it at 60fps and you are up to 9million callbacks a second. I think you can spot a problem here.
  • Stop passing strings around in those function calls too.

To get this more performance, remove the OOP stuff and go for ugly spaghetti code, only where it makes sense.

Collision detection can be optimized by not testing every particle against each other. Just look up quadtrees. Not that hard to implement, and the basics of it can be used to e up with a custom solution.

Since you are doing quite some vector math, try out the glmatrix library. Optimized vector math :-)

I don't know what major improvement you can do here except switching to a technology that uses hardware acceleration.

I hope this helps a bit, though as stated in question's ments WebGL would be really faster. If you don't know where to start, here is a good one: webglacademy

Still I saw some little thingies:

radialWave : function(particle, pIndex, particles){

        // As you don't use distance here remove this line
        // it's a really greedy calculus that involves square root
        // always avoid if you don't have to use it

        // var distance = getDistance(particle, this.center);

        if (particle.radius >= (this.dim * 0.0085)) {
            particle.radiusOper = -0.02;
        } else if (particle.radius <= 1) {
            particle.radiusOper = 0.02;
        }

        particle.radius += particle.radiusOper * particle.radius;
    },

Another little thingy:

Jarvis.prototype.backgroundDraw = function() {

    // particles

    var that = this;

    // Declare callbacks outside of forEach calls
    // it will save you a function declaration each time you loop

    // Do this for logo particles
    var logoMotionCallback = function(motionType, motionIndex){
        // Another improvement may be to use a direct function that does not use 'this'
        // and instead pass this with a parameter called currentParticle for example
        // call and apply are known to be pretty heavy -> see if you can avoid this
        that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
    };

    var logoFxCallback = function(fxType, fxIndex){
        that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles");
    };

    var logoCollisionCallback = function(collisionType, collisionIndex){
        that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles");
    };

    this.logoParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.logoParticles.motion.forEach(motionCallback);
        that.logoParticles.fx.forEach(fxCallback);
        that.logoParticles.collision.forEach(collisionCallback);
    });

    // Now do the same for background particles
    var bgMotionCallback = function(motionType, motionIndex){
            that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
    };

    var bgFxCallback = function(fxType, fxIndex){
        that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
    };

    var bgCollisionCallback = function(collisionType, collisionIndex){
        that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles");
    };

    this.backgroundParticles.forEach(function(particle, i){

        particle.draw(that.backgroundCtx);

        that.backgroundParticles.motion.forEach(bgMotionCallback);
        that.backgroundParticles.fx.forEach(bgFxCallback);
        that.backgroundParticles.collision.forEach(bgCollisionCallback);
    });

}

I think you might find that webworker support is about equal to WebGL support:

WebGL Support: http://caniuse./#search=webgl
WebWorker Support: http://caniuse./#search=webworker

On the surface they may appear to be different, but they aren't really. The only thing you'll gain is IE10 support temporarily. IE11 has already surpassed IE10 in market share and the divide will continue to grow. The only thing to note is that webgl support also seems to be based on updated graphics card drivers.

Of course, I don't know your specific needs so maybe this doesn't work.

Options

Wait what? 200 items on screen is slow?

  • +1 to everything DoKick said
  • Have you considered using typed arrays: https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array to have a good API and use that you'll need to go through some hoops.
  • I'd look at this example since it does many similar things: http://threejs/examples/#canvas_particles_shapes

Do less in canvas land and do the cool stuff in WebGL

Many libraries do this. Canvas should be usable and just a little cool. WebGL generally has all the cool particle features.

WebWorkers

You'll likely need to use a deferred library or create a system that figures out when all the webworkers are done and have a pool of worker threads.

Some caveats:

  1. You can't access anything from your main application, and must municate through events
  2. Objects passed through a webworker are copied not shared
  3. Setting up webworkers without a separate script can require some research

Unconfirmed Rumor: I've heard that there's a limited amount of data you can pass through the web worker messages. You should test this since it seems directly applicable to your use case.

发布评论

评论列表(0)

  1. 暂无评论