Рисование нескольких элементов particle на холсте html5 без уничтожения рендеринга

#javascript #html #canvas

#javascript #HTML #холст

Вопрос:

В приведенном ниже коде я запускаю / останавливаю поток частиц при нажатии на блок. Это работает нормально, однако мне нужно иметь около 10-20 языков пламени, способных запускаться / останавливаться по отдельности и отслеживать их. Друг предложил мне поместить каждый flame в небольшой холст, затем нарисовать каждый холст по отдельности, что, я думаю, было бы излишеством при рендеринге, если бы одновременно выполнялось 10-20 draw() . Как мне это сделать?

Следует отметить, что положение пламени задается в particle(), затем создается массив частиц, который в основном представляет пламя.

 // 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 amp;amp; (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

Комментарии:

1. Ха-ха, я узнаю fog of war codepen, из которого вы позаимствовали некоторые фрагменты кода.

2. да, это был потрясающий пример: D

Ответ №1:

Вероятно, вы могли бы повысить производительность, если бы предварительно сгенерировали частицы в нескольких небольших холстах. В основном генерируйте список изображений частиц разных цветов и размеров, а затем используйте их для всех частиц. Непрозрачность все еще может быть применена при рисовании изображения particle. Должно быть быстрее просто нарисовать небольшой холст в заданном положении с заданной непрозрачностью, чем рисовать контур с радиальным градиентом.

Вот пример, который, похоже, удваивает производительность как минимум: http://codepen.io/anon/pen/Izqwu

Я не создаю предварительно холсты particle. Вместо этого я написал функцию getParticleCanvas() , которая принимает цвет и возвращает холст размером 32 * 32 пикселя (создает его один раз, если он не существует), который затем используется в drawing(). Затем холст particale отрисовывается с правильным размером и непрозрачностью. Позиция округляется до ближайшего пикселя для повышения производительности.

Кроме того, чтобы уменьшить количество возможных холстов particle, случайные цвета округляются до 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.

Ответ №2:

Я получил некоторую внешнюю поддержку и в конечном итоге сделал это вот так, и я думаю, что это лучший способ сохранить производительность:

Создайте небольшой фиктивный холст в памяти для запуска draw() flame

 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 в зависимости от щелчка мыши.