Избегайте, насколько это возможно, полупрозрачности.
Рисование с помощью альфа-канала убивает процессор, избегайте максимально возможного смешивания с использованием сплошных цветов:
// Global Animation Setting
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000/60);
};
// Global Canvas Setting
var canvas = document.getElementById('particle');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Particles Around the Parent
function Particle(x, y, distance) {
this.angle = Math.random() * 2 * Math.PI;
this.radius = Math.random() ;
this.opacity = (Math.random()*5 + 2)/10;
// convert to solid color '#nnnnnn'
this.color = '#' + Math.floor((this.opacity * 255)).toString(16).padStart(2, 0).repeat(3);
this.distance = (1/this.opacity)*distance;
this.speed = this.distance*0.00003;
this.position = {
x: x + this.distance * Math.cos(this.angle),
y: y + this.distance * Math.sin(this.angle)
};
this.draw = function() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, false);
ctx.fill();
ctx.closePath();
}
this.update = function() {
this.angle += this.speed;
this.position = {
x: x + this.distance * Math.cos(this.angle),
y: y + this.distance * Math.sin(this.angle)
};
this.draw();
}
}
function Emitter(x, y) {
this.position = { x: x, y: y};
this.radius = 30;
this.count = 3000;
this.particles = [];
for(var i=0; i< this.count; i ++ ){
this.particles.push(new Particle(this.position.x, this.position.y, this.radius));
}
}
Emitter.prototype = {
draw: function() {
ctx.fillStyle = "rgba(0,0,0,1)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, false);
ctx.fill();
ctx.closePath();
},
update: function() {
for(var i=0; i< this.count; i++) {
this.particles[i].update();
}
this.draw();
}
}
var emitter = new Emitter(canvas.width/2, canvas.height/2);
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
emitter.update();
requestAnimFrame(loop);
}
loop();
body{background:#000;}
<canvas id="particle"></canvas>
Но этого по-прежнему недостаточно,
Избегайте рисовать как можно больше.
Операции рисования выполняютсяочень медленно на холсте (по сравнению с неокрашенными) и его следует избегать как можно больше.Чтобы сделать это, вы можете отсортировать частицы по цвету и нарисовать их по стеку отдельных объектов Path, но для этого необходимо немного округлить значение opacity
(это делается, когда затвердевает цвет).
// Global Animation Setting
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000/60);
};
// Global Canvas Setting
var canvas = document.getElementById('particle');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Particles Around the Parent
function Particle(x, y, distance) {
this.angle = Math.random() * 2 * Math.PI;
this.radius = Math.random() ;
this.opacity = (Math.random()*5 + 2)/10;
// convert to solid color '#nnnnnn'
this.color = '#' + Math.floor((this.opacity * 255)).toString(16).padStart(2, 0).repeat(3);
this.distance = (1/this.opacity)*distance;
this.speed = this.distance*0.00003;
this.position = {
x: x + this.distance * Math.cos(this.angle),
y: y + this.distance * Math.sin(this.angle)
};
this.draw = function() {
// here we remove everything but the 'arc' operation and a moveTo
// no paint
ctx.moveTo(this.position.x + this.radius, this.position.y);
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, false);
}
this.update = function() {
this.angle += this.speed;
this.position = {
x: x + this.distance * Math.cos(this.angle),
y: y + this.distance * Math.sin(this.angle)
};
// 'update' should not 'draw'
// this.draw();
}
}
function Emitter(x, y) {
this.position = { x: x, y: y};
this.radius = 30;
this.count = 3000;
this.particles = [];
for(var i=0; i< this.count; i ++ ){
this.particles.push(new Particle(this.position.x, this.position.y, this.radius));
}
// sort our particles by color (opacity = color)
this.particles.sort(function(a, b) {
return a.opacity - b.opacity;
});
}
Emitter.prototype = {
draw: function() {
ctx.fillStyle = "rgba(0,0,0,1)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, false);
ctx.fill();
// draw our particles in batches
var particle, color;
ctx.beginPath();
for(var i=0; i<this.count; i++) {
particle = this.particles[i];
if(color !== particle.color) {
ctx.fill();
ctx.beginPath();
ctx.fillStyle = color = particle.color;
}
particle.draw();
}
ctx.fill(); // fill the last batch
},
update: function() {
for(var i=0; i< this.count; i++) {
this.particles[i].update();
}
this.draw();
}
}
var emitter = new Emitter(canvas.width/2, canvas.height/2);
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
emitter.update();
requestAnimFrame(loop);
}
loop();
body{background:#000;}
<canvas id="particle"></canvas>
Это лучше, но еще не идеально ...
Наконец, будьте умны в отношении ВАШЕЙ анимации.
В вашей анимации непрозрачность определяет расстояние.То есть частицы, находящиеся дальше от центра, являются наиболее прозрачными.Это точно определяет, что такое радиальный градиент .
Таким образом, мы можем сократить количество операций рисования до двух.Да, только две краски для 3000 частиц, используя радиальный градиент и немного компоновки, мы можем сначала нарисовать все частиц в одном кадре, а затем применить градиент в качестве маски, которая будет применять егоцвет только там, где уже было что то нарисовано.Мы даже можем сохранить прозрачность.
// Global Animation Setting
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000/60);
};
// Global Canvas Setting
var canvas = document.getElementById('particle');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Particles Around the Parent
function Particle(x, y, distance) {
this.angle = Math.random() * 2 * Math.PI;
this.radius = Math.random() ;
this.opacity = (Math.random()*5 + 2)/10;
this.distance = (1/this.opacity)*distance;
this.speed = this.distance*0.00003;
this.position = {
x: x + this.distance * Math.cos(this.angle),
y: y + this.distance * Math.sin(this.angle)
};
this.draw = function() {
// still no paint here
ctx.moveTo(this.position.x + this.radius, this.position.y);
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, false);
}
this.update = function() {
this.angle += this.speed;
this.position = {
x: x + this.distance * Math.cos(this.angle),
y: y + this.distance * Math.sin(this.angle)
};
this.draw();
}
}
function Emitter(x, y) {
this.position = { x: x, y: y};
this.radius = 30;
this.count = 3000;
this.particles = [];
for(var i=0; i< this.count; i ++ ){
this.particles.push(new Particle(this.position.x, this.position.y, this.radius));
}
// a radial gradient that we will use as mask
// in particle.constructor
// opacities go from 0.2 to 0.7
// with a distance range of [radius, 1 / 0.2 * this.radius]
this.grad = ctx.createRadialGradient(x, y, this.radius, x, y, 1 / 0.2 * this.radius);
this.grad.addColorStop(0, 'rgba(255,255,255,0.7)');
this.grad.addColorStop(1, 'rgba(255,255,255,0.2)');
}
Emitter.prototype = {
draw: function() {
ctx.fillStyle = "rgba(0,0,0,1)";
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, Math.PI*2, false);
ctx.fill();
ctx.closePath();
},
update: function() {
ctx.beginPath(); // one Path
ctx.fillStyle = 'black'; // a solid color
for(var i=0; i< this.count; i++) {
this.particles[i].update();
}
ctx.fill(); // one paint
// prepare the composite operation
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = this.grad; // our gradient
ctx.fillRect(0,0,canvas.width, canvas.height); // cover the whole canvas
// reset for next paints (center arc and next frame's clearRect)
ctx.globalCompositeOperation = 'source-over';
this.draw();
}
}
var emitter = new Emitter(canvas.width/2, canvas.height/2);
function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
emitter.update();
requestAnimFrame(loop);
}
loop();
body{background:#000;}
<canvas id="particle"></canvas>