HTML5 Canvas Particle Explosion

I'm trying to get this explosion of particles to work. It works, but it looks like some frames are not displayed. If I press many times to cause several explosions, it will start blinking ... "lag / stutter". Is there something I forgot to do? It may look like the browser freezes when I click many times. Is it too much to have 2 for loops inside each other?

Attached my code so you can see. Just try to click many times and you will see the problem visually.

// Request animation frame var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; // Canvas var c = document.getElementById('canvas'); var ctx = c.getContext('2d'); // Set full-screen c.width = window.innerWidth; c.height = window.innerHeight; // Options var background = '#333'; // Background color var particlesPerExplosion = 20; var particlesMinSpeed = 3; var particlesMaxSpeed = 6; var particlesMinSize = 1; var particlesMaxSize = 3; var explosions = []; var fps = 60; var now, delta; var then = Date.now(); var interval = 1000 / fps; // Optimization for mobile devices if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { fps = 29; } // Draw function draw() { // Loop requestAnimationFrame(draw); // Set NOW and DELTA now = Date.now(); delta = now - then; // New frame if (delta > interval) { // Update THEN then = now - (delta % interval); // Our animation drawBackground(); drawExplosion(); } } // Draw explosion(s) function drawExplosion() { if (explosions.length == 0) { return; } for (var i = 0; i < explosions.length; i++) { var explosion = explosions[i]; var particles = explosion.particles; if (particles.length == 0) { explosions.splice(i, 1); return; } for (var ii = 0; ii < particles.length; ii++) { var particle = particles[ii]; // Check particle size // If 0, remove if (particle.size < 0) { particles.splice(ii, 1); return; } ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false); ctx.closePath(); ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')'; ctx.fill(); // Update particle.x += particle.xv; particle.y += particle.yv; particle.size -= .1; } } } // Draw the background function drawBackground() { ctx.fillStyle = background; ctx.fillRect(0, 0, c.width, c.height); } // Clicked function clicked(e) { var xPos, yPos; if (e.offsetX) { xPos = e.offsetX; yPos = e.offsetY; } else if (e.layerX) { xPos = e.layerX; yPos = e.layerY; } explosions.push(new explosion(xPos, yPos)); } // Explosion function explosion(x, y) { this.particles = []; for (var i = 0; i < particlesPerExplosion; i++) { this.particles.push(new particle(x, y)); } } // Particle function particle(x, y) { this.x = x; this.y = y; this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false); this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false); this.size = randInt(particlesMinSize, particlesMaxSize, true); this.r = randInt(113, 222); this.g = '00'; this.b = randInt(105, 255); } // Returns an random integer, positive or negative // between the given value function randInt(min, max, positive) { if (positive == false) { var num = Math.floor(Math.random() * max) - min; num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1; } else { var num = Math.floor(Math.random() * max) + min; } return num; } // On-click $('canvas').on('click', function(e) { clicked(e); }); draw(); 
 <!DOCTYPE html> <html> <head> <style> * { margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <canvas id="canvas"></canvas> </body> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> </html> 
+5
source share
2 answers

You return from iterating over particles if they are too small. This leads to the fact that other particles of this explosion will be displayed only in the next frame.

I have a working version:

 // Request animation frame const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; // Canvas const c = document.getElementById('canvas'); const ctx = c.getContext('2d'); // Set full-screen c.width = window.innerWidth; c.height = window.innerHeight; // Options const background = '#333'; // Background color const particlesPerExplosion = 20; const particlesMinSpeed = 3; const particlesMaxSpeed = 6; const particlesMinSize = 1; const particlesMaxSize = 3; const explosions = []; let fps = 60; const interval = 1000 / fps; let now, delta; let then = Date.now(); // Optimization for mobile devices if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { fps = 29; } // Draw function draw() { // Loop requestAnimationFrame(draw); // Set NOW and DELTA now = Date.now(); delta = now - then; // New frame if (delta > interval) { // Update THEN then = now - (delta % interval); // Our animation drawBackground(); drawExplosion(); } } // Draw explosion(s) function drawExplosion() { if (explosions.length === 0) { return; } for (let i = 0; i < explosions.length; i++) { const explosion = explosions[i]; const particles = explosion.particles; if (particles.length === 0) { explosions.splice(i, 1); return; } const particlesAfterRemoval = particles.slice(); for (let ii = 0; ii < particles.length; ii++) { const particle = particles[ii]; // Check particle size // If 0, remove if (particle.size <= 0) { particlesAfterRemoval.splice(ii, 1); continue; } ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false); ctx.closePath(); ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')'; ctx.fill(); // Update particle.x += particle.xv; particle.y += particle.yv; particle.size -= .1; } explosion.particles = particlesAfterRemoval; } } // Draw the background function drawBackground() { ctx.fillStyle = background; ctx.fillRect(0, 0, c.width, c.height); } // Clicked function clicked(e) { let xPos, yPos; if (e.offsetX) { xPos = e.offsetX; yPos = e.offsetY; } else if (e.layerX) { xPos = e.layerX; yPos = e.layerY; } explosions.push( new explosion(xPos, yPos) ); } // Explosion function explosion(x, y) { this.particles = []; for (let i = 0; i < particlesPerExplosion; i++) { this.particles.push( new particle(x, y) ); } } // Particle function particle(x, y) { this.x = x; this.y = y; this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false); this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false); this.size = randInt(particlesMinSize, particlesMaxSize, true); this.r = randInt(113, 222); this.g = '00'; this.b = randInt(105, 255); } // Returns an random integer, positive or negative // between the given value function randInt(min, max, positive) { let num; if (positive === false) { num = Math.floor(Math.random() * max) - min; num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1; } else { num = Math.floor(Math.random() * max) + min; } return num; } // On-click $('canvas').on('click', function (e) { clicked(e); }); draw(); 
 <!DOCTYPE html> <html> <head> <style>* {margin:0;padding:0;overflow:hidden;}</style> </head> <body> <canvas id="canvas"></canvas> </body> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> </html> 
+4
source

Loops, break and continued.

The problem was caused when you checked the empty arrays of particles and found that the particle was removed.

Mistakes

The following two statements and blocks caused the problem.

 if (particles.length == 0) { explosions.splice(i, 1); return; } 

and

 if (particles.size < 0) { explosions.splice(ii, 1); return; } 

Returns stopped rendering particles, so you sometimes returned before drawing a single particle that was visualized only because the first explosion was empty or the first particle was too small.

Continue and break

You can use the continuation marker in javascript to skip the rest of a, while, do loop

 for(i = 0; i < 100; i++){ if(test(i)){ // need to skip this iteration continue; } // more code // more code // continue skips all the code upto the closing } } << continues to here and if i < 100 the loop continues on. 

Or you can completely break out of a loop with break

 for(i = 0; i < 100; i++){ if(test(i)){ // need to exit the for loop break; } // more code // more code // break skips all the code to the first line after the closing } } << breaks to here and if i remains the value it was when break was encountered 

Correction

 if (particles.length == 0) { explosions.splice(i, 1); continue; } 

and

 if (particles.size < 0) { explosions.splice(ii, 1); continue; } 

Your fix example

Your corrected code. Befor I found it, I started changing things.

Minor things. requestAnimationFrame transfers time in milliseconds accurate to microseconds.

You incorrectly set and lost frames. I changed the time to use the time of the argument, and then just set to the time when the frame is drawn.

There are other problems, nothing important and no more coding style style. You must capitalize on objects created with the new

 function Particle(... 

not

 function Particle(... 

and your random is too complicated

 function randInt(min, max = min - (min = 0)) { return Math.floor(Math.random() * (max - min) + min); } or function randInt(min,max){ max = max === undefined ? min - (min = 0) : max; return Math.floor(Math.random() * (max - min) + min); } randInt(100); // int 0 - 100 randInt(10,20); // int 10-20 randInt(-100); // int -100 to 0 randInt(-10,20); // int -10 to 20 this.xv = randInt(-particlesMinSpeed, particlesMaxSpeed); this.yv = randInt(-particlesMinSpeed, particlesMaxSpeed); this.size = randInt(particlesMinSize, particlesMaxSize); 

And if you use the same name in variables, then a good sign for creating an object

 var particlesPerExplosion = 20; var particlesMinSpeed = 3; var particlesMaxSpeed = 6; var particlesMinSize = 1; var particlesMaxSize = 3; 

May be

 const settings = { particles : { speed : {min : 3, max : 6 }, size : {min : 1 : max : 3 }, explosionCount : 20, }, background : "#000", } 

Anyway, your code.

 var c = canvas; var ctx = c.getContext('2d'); // Set full-screen c.width = innerWidth; c.height = innerHeight; // Options var background = '#333'; // Background color var particlesPerExplosion = 20; var particlesMinSpeed = 3; var particlesMaxSpeed = 6; var particlesMinSize = 1; var particlesMaxSize = 3; var explosions = []; var fps = 60; var now, delta; var then = 0; // Zero start time var interval = 1000 / fps; // Optimization for mobile devices if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { fps = 29; } // Draw // as time is passed you need to start with requestAnimationFrame requestAnimationFrame(draw); function draw(time) { //requestAnimationFrame frame passes the time requestAnimationFrame(draw); delta = time - then; if (delta > interval) { then = time drawBackground(); drawExplosion(); } } // Draw explosion(s) function drawExplosion() { if (explosions.length == 0) { return; } for (var i = 0; i < explosions.length; i++) { var explosion = explosions[i]; var particles = explosion.particles; if (particles.length == 0) { explosions.splice(i, 1); //return; continue; } for (var ii = 0; ii < particles.length; ii++) { var particle = particles[ii]; // Check particle size // If 0, remove if (particle.size < 0) { particles.splice(ii, 1); // return; continue; } ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false); ctx.closePath(); ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')'; ctx.fill(); // Update particle.x += particle.xv; particle.y += particle.yv; particle.size -= .1; } } } // Draw the background function drawBackground() { ctx.fillStyle = background; ctx.fillRect(0, 0, c.width, c.height); } // Clicked function clicked(e) { var xPos, yPos; if (e.offsetX) { xPos = e.offsetX; yPos = e.offsetY; } else if (e.layerX) { xPos = e.layerX; yPos = e.layerY; } explosions.push(new explosion(xPos, yPos)); } // Explosion function explosion(x, y) { this.particles = []; for (var i = 0; i < particlesPerExplosion; i++) { this.particles.push(new particle(x, y)); } } // Particle function particle(x, y) { this.x = x; this.y = y; this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false); this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false); this.size = randInt(particlesMinSize, particlesMaxSize, true); this.r = randInt(113, 222); this.g = '00'; this.b = randInt(105, 255); } // Returns an random integer, positive or negative // between the given value function randInt(min, max, positive) { if (positive == false) { var num = Math.floor(Math.random() * max) - min; num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1; } else { var num = Math.floor(Math.random() * max) + min; } return num; } // On-click $('canvas').on('click', function(e) { clicked(e); }); 
 <!DOCTYPE html> <html> <head> <style> * { margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <canvas id="canvas"></canvas> </body> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> </html> 
+1
source

Source: https://habr.com/ru/post/1266923/


All Articles