在html5画布上绘制多个粒子元素而不使用渲染杀死

时间:2014-07-06 20:44:18

标签: javascript html5 canvas

在下面的代码中,我点击块时开始/停止粒子火焰。它工作正常,但是,我需要有大约10-20个火焰能够单独启动/停止并跟踪它们。一位朋友建议我将每个火焰放在一个小画布上,然后单独绘制每个画布,我认为这将是一个渲染矫枉过正,同时运行10-20 draw()。我该怎么办呢?

有一点需要注意的是,火焰的位置是在粒子()中给出的,然后会产生一个粒子阵列,它基本上代表火焰。

// init canvas
var canvas = $('canvas'),
    ctx = canvas[0].getContext('2d') // world
    ,
    ctx2 = canvas[1].getContext('2d') // fog
    ,
    context = canvas[2].getContext('2d') // flame
        ,
    mDown = false,
    r1 = 100,
    r2 = 300,
    density = .4,
    hideOnMove = true,
    hideFill = 'rgba( 0, 0, 0, 1 )'
    ,
    overlay = 'rgba( 0, 0, 0, 1 )',
    particles = [],
    particle_count = 100;
// init flame
for (var i = 0; i < particle_count; i++) {
    particles.push(new particle());
}

if (!hideOnMove) {
    // shouldn't be done like this, but this is a demo
    canvas.get(1).remove();
}

// black out the canvas
ctx.fillStyle = overlay;
ctx.fillRect(0, 0, 1280, 800);

// set up our "eraser"
ctx.globalCompositeOperation = 'destination-out';

canvas.last()
    .on('mousemove', function (ev, ev2) {
        ev2 && (ev = ev2);

        var pX = ev.pageX,
            pY = ev.pageY;

        // reveal wherever we drag
        var radGrd = ctx.createRadialGradient(pX, pY, r1, pX, pY, r2);
        radGrd.addColorStop(0, 'rgba( 0, 0, 0,  1 )');
        radGrd.addColorStop(density, 'rgba( 0, 0, 0, .1 )');
        radGrd.addColorStop(1, 'rgba( 0, 0, 0,  0 )');

        ctx.fillStyle = radGrd;
        ctx.fillRect(pX - r2, pY - r2, r2 * 2, r2 * 2);

        // partially hide the entire map and re-reval where we are now
        ctx2.globalCompositeOperation = 'source-over';
        ctx2.clearRect(0, 0, 1280, 800);
        ctx2.fillStyle = hideFill;
        ctx2.fillRect(0, 0, 1280, 800);

        var radGrd = ctx.createRadialGradient(pX, pY, r1, pX, pY, r2);
        radGrd.addColorStop(0, 'rgba( 0, 0, 0,  1 )');
        radGrd.addColorStop(.8, 'rgba( 0, 0, 0, .1 )');
        radGrd.addColorStop(1, 'rgba( 0, 0, 0,  0 )');

        ctx2.globalCompositeOperation = 'destination-out';
        ctx2.fillStyle = radGrd;
        ctx2.fillRect(pX - r2, pY - r2, r2 * 2, r2 * 2);

    })
    .trigger('mousemove', {
        pageX: 150,
        pageY: 150
    });


function drawing() {
    // clear canvas
    context.clearRect(0, 0, 1280, 800);
    context.globalCompositeOperation = "lighter";

    for (var i = 0; i < particles.length; i++) {
        var p = particles[i];
        context.beginPath();
        //changing opacity according to the life.
        //opacity goes to 0 at the end of life of a particle
        p.opacity = Math.round(p.remaining_life / p.life * 100) / 100
            //a gradient instead of white fill
        var gradient = context.createRadialGradient(p.location.x, p.location.y, 0, p.location.x, p.location.y, p.radius);
        gradient.addColorStop(0, "rgba(" + p.r + ", " + p.g + ", " + p.b + ", " + p.opacity + ")");
        gradient.addColorStop(0.5, "rgba(" + p.r + ", " + p.g + ", " + p.b + ", " + p.opacity + ")");
        gradient.addColorStop(1, "rgba(" + p.r + ", " + p.g + ", " + p.b + ", 0)");
        context.fillStyle = gradient;
        context.arc(p.location.x, p.location.y, p.radius, Math.PI * 2, false);
        context.fill();

        //lets move the particles
        p.remaining_life--;
        p.radius--;
        p.location.x += p.speed.x;
        p.location.y += p.speed.y;

        //regenerate particles
        if (p.remaining_life < 0 || p.radius < 0) {
            //a brand new particle replacing the dead one
            particles[i] = new particle();
        }
    }
}

// set flame on/off
var myVar = 0;
var on = 0;
$('.c').css({
    left: "610px",
    top: "500px"
});
$('.c').click(function () {
    if (on == 0) {
        myVar = setInterval(drawing, 33);
        on = 1;
    } else {
        clearInterval(myVar);
        context.clearRect(0, 0, 1280, 800);
        on = 0;
    }
});

function particle() {
    //speed, life, location, life, colors
    //speed.x range = -2.5 to 2.5 
    //speed.y range = -15 to -5 to make it move upwards
    //lets change the Y speed to make it look like a flame
    this.speed = {
        x: -2.5 + Math.random() * 5,
        y: -15 + Math.random() * 10
    };
    //flame location
    this.location = {
        x: 640,
        y: 520
    };
    //radius range = 10-30
    this.radius = 10 + Math.random() * 20;
    //life range = 20-30
    this.life = 20 + Math.random() * 10;
    this.remaining_life = this.life;
    //colors
    this.r = Math.round(Math.random() * 255);
    this.g = Math.round(Math.random() * 255);
    this.b = Math.round(Math.random() * 255);
}

请在此处查看完整网页http://codepen.io/anon/pen/hxrat

2 个答案:

答案 0 :(得分:0)

如果您在一堆小画布中预先生成粒子,则可能会提高性能。基本上生成具有不同颜色和大小的粒子图像列表,然后将其用于所有粒子。绘制粒子图像时仍可应用不透明度。在具有给定不透明度的给定位置绘制小画布应该更快,而不是绘制具有径向渐变的路径。

以下是一个示例,似乎至少使性能翻倍:http://codepen.io/anon/pen/Izqwu

我不预先生成粒子画布。相反,我编写了一个函数getParticleCanvas(),它采用一种颜色并返回一个32 * 32像素的画布(如果它不存在则创建一次),然后在drawing()中使用。然后以正确的大小和不透明度绘制粒子画布。为了表现,该位置四舍五入到最近的像素。

此外,为了减少可能的粒子画布数量,每个通道的随机颜色四舍五入到8个不同的步骤:

this.r = Math.round(Math.random() * 8)*32;
this.g = Math.round(Math.random() * 8)*32;
this.b = Math.round(Math.random() * 8)*32;

你可以将getParticleCanvas()中的半径从16减少到8而不会引起注意。

答案 1 :(得分:0)

我得到了一些外界支持,并最终这样做,我认为这是保持绩效的最佳方式:

在内存中创建一个小虚拟画布以运行火焰的draw()

var flameCanvas = document.createElement('canvas');
flameCanvas.width = 100;
flameCanvas.height = 400;
var context = flameCanvas.getContext('2d');

使用大画布在任何需要的地方显示虚拟画布的副本。

ctx3.drawImage(flameCanvas, 420, -37, 50, 200);
ctx3.drawImage(flameCanvas, 325, -47, 60, 240);
...

渲染在虚拟画布中运行,图像只是全部复制,这样可以提高性能。唯一的缺点是所有的火焰都是一样的。

为了跟踪它们,我用if子句包围每个

if (centerFlame_on) {
        ctx3.drawImage(flameCanvas, 590, 165);
    }

我根据鼠标点击将centerFlame_on更新为true / false。