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