javascript - 在 html5 Canvas 上绘制多个粒子元素而无需渲染终止

标签 javascript html canvas

在下面的代码中,我在单击 block 时启动/停止粒子火焰。它工作正常,但是,我需要大约 10-20 个火焰能够单独启动/停止并跟踪它们。一位 friend 建议我将每个火焰放在一个小 Canvas 中,然后单独绘制每个 Canvas ,我认为这会导致渲染过度,同时运行 10-20 个draw()。我该怎么办?

需要注意的一点是,火焰的位置在article()中给出,然后创建一个粒子数组,它基本上代表了火焰。

// 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

最佳答案

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

这是一个示例,似乎至少使性能提高了一倍:http://codepen.io/anon/pen/Izqwu

我不预先生成粒子 Canvas 。相反,我编写了一个函数 getParticleCanvas() ,它接受颜色并返回 32*32 像素 Canvas (如果不存在则创建一次),然后在 Drawing() 中使用。然后以正确的尺寸和不透明度绘制粒子 Canvas 。为了提高性能,该位置四舍五入到最接近的像素。

此外,为了减少可能的粒子 Canvas 数量,随机颜色四舍五入为每个 channel 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,而不会被注意到。

关于javascript - 在 html5 Canvas 上绘制多个粒子元素而无需渲染终止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24600164/

相关文章:

javascript - 鼠标移动使div平滑倒转

javascript - PWA 应用的自动更新解决方案

javascript - 悬停 2 秒后提交表单

html - flexbox/网格布局中的最后边距/填充折叠

javascript - 用于圆形的 HTML5 Canvas ctx.clip() 方法在圆形下方留下一条线

javascript - 在 SVG 元素中保留变换

html - 如何使用 border-radius 在 CSS3 中创建三 Angular 形

javascript - 如何在径向进度条上显示部分?

css - 如何在网页中显示手机壳背面(带有透明相机孔)等形状内的图像?

html - 无法调整 PayPal 按钮上方和下方的填充/间距?