The Code:
NOTE: For brevity, the general HTML setup is omitted.
The following is in the <body> section
<!-----------------------------------------------
// Setup the canvas --- with a NAME!
// -------------------------------------------->
<canvas id="myCanvas" width="640" height="480" style="border:1px solid #a1a1a1;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<!-----------------------------------------------
// Particle code follows
// -------------------------------------------->
<script>
// Initializing the canvas
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// Save some typing later
var W = canvas.width;
var H = canvas.height;
// Declare variables
var particles = [];
var mouse = {};
// Add mouse tracking
canvas.addEventListener('mousemove', track_mouse, false);
// Mouse tracking function
function track_mouse(event)
{
var rect = canvas.getBoundingClientRect();
mouse.x = event.clientX - rect.left;
mouse.y = event.clientY - rect.top;
}
// Create the particles
var particle_count = 100;
for(var i = 0; i < particle_count; i++)
{
particles.push(new particle());
}
// Function to create a particle (used in for-loop above)
function particle()
{
// Particle data: speed, radius, location, life, colors
// Range of speed.x = -2.5 to 2.5
// Range of speed.y = -15 to -5 --> negatives mean move upwards (fire like)
this.speed = {x: -2.5+Math.random()*5, y: -15+Math.random()*10};
// Radius can be from 10 to 30
this.radius = 10 + Math.random()*20;
// Follow the mouse position
if (mouse.x && mouse.y)
{
this.location = {x: mouse.x, y: mouse.y};
}
else // mouse has not been in canvas area so center the flame
{
this.location = {x: W/2, y: H/2};
}
// Particles live to be 20 to 30 updates (time steps old)
this.life = 20+Math.random()*10;
this.remaining_life = this.life;
// Color in red, green, blue ranging from 0 to 255 for each
// But we want mostly reddish colors, so set ranges appropriately =)
this.r = Math.round(Math.random()*255);
this.g = Math.round(Math.random()*100);
this.b = Math.round(Math.random()*50);
// Alpha is expressed from 0.0 to 1.0, where 1.0 is fully opaque
this.opacity = 1.0;
} // end particle()
// Now we need to render and update the particles
// Begin with just the render
function draw()
{
// Setup for blending
// Here globalCompositeOperation tricks are used for blending effects
// The canvas is painted solid black - over the previous frame
// Effectively erasing it
// Particles are painted using the "lighter" option
// This blends them using their opacity
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = "lighter";
for(var i = 0; i < particles.length; i++)
{
var p = particles[i];
ctx.beginPath();
// Render the particle
// For blending to work we use gradient for the fillStyle
var gradient = ctx.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)");
ctx.fillStyle = gradient;
ctx.arc(p.location.x, p.location.y, p.radius, Math.PI*2, false);
ctx.fill();
}
} // end draw()
function updatePart()
{
for(var i = 0; i < particles.length; i++)
{
var p = particles[i];
p.remaining_life--;
p.radius--;
p.location.x += p.speed.x;
p.location.y += p.speed.y;
// Adjust the alpha/opacity setting based on age
// Opacity value is based on life of particle
// It goes to zero as the particle gets older
p.opacity = Math.round(p.remaining_life / p.life * 100) / 100;
// Remove the dead and immediately reincarnate -- make them reborn at new location
if (p.remaining_life < 0 || p.radius < 0)
{
// Recall p = particles[i]
particles[i] = new particle();
}
}
} // end updatePart
// Call update and draw -- at same rate <--- is this a good or bad idea? Why?
setInterval(updatePart, 33);
// For anything to happen must call draw (i.e. render something)
setInterval(draw, 33);
</script>
The following is in the <body> section
<!-----------------------------------------------
// Setup the canvas --- with a NAME!
// -------------------------------------------->
<canvas id="myCanvas" width="640" height="480" style="border:1px solid #a1a1a1;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<!-----------------------------------------------
// Particle code follows
// -------------------------------------------->
<script>
// Initializing the canvas
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// Save some typing later
var W = canvas.width;
var H = canvas.height;
// Declare variables
var particles = [];
var mouse = {};
// Add mouse tracking
canvas.addEventListener('mousemove', track_mouse, false);
// Mouse tracking function
function track_mouse(event)
{
var rect = canvas.getBoundingClientRect();
mouse.x = event.clientX - rect.left;
mouse.y = event.clientY - rect.top;
}
// Create the particles
var particle_count = 100;
for(var i = 0; i < particle_count; i++)
{
particles.push(new particle());
}
// Function to create a particle (used in for-loop above)
function particle()
{
// Particle data: speed, radius, location, life, colors
// Range of speed.x = -2.5 to 2.5
// Range of speed.y = -15 to -5 --> negatives mean move upwards (fire like)
this.speed = {x: -2.5+Math.random()*5, y: -15+Math.random()*10};
// Radius can be from 10 to 30
this.radius = 10 + Math.random()*20;
// Follow the mouse position
if (mouse.x && mouse.y)
{
this.location = {x: mouse.x, y: mouse.y};
}
else // mouse has not been in canvas area so center the flame
{
this.location = {x: W/2, y: H/2};
}
// Particles live to be 20 to 30 updates (time steps old)
this.life = 20+Math.random()*10;
this.remaining_life = this.life;
// Color in red, green, blue ranging from 0 to 255 for each
// But we want mostly reddish colors, so set ranges appropriately =)
this.r = Math.round(Math.random()*255);
this.g = Math.round(Math.random()*100);
this.b = Math.round(Math.random()*50);
// Alpha is expressed from 0.0 to 1.0, where 1.0 is fully opaque
this.opacity = 1.0;
} // end particle()
// Now we need to render and update the particles
// Begin with just the render
function draw()
{
// Setup for blending
// Here globalCompositeOperation tricks are used for blending effects
// The canvas is painted solid black - over the previous frame
// Effectively erasing it
// Particles are painted using the "lighter" option
// This blends them using their opacity
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = "lighter";
for(var i = 0; i < particles.length; i++)
{
var p = particles[i];
ctx.beginPath();
// Render the particle
// For blending to work we use gradient for the fillStyle
var gradient = ctx.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)");
ctx.fillStyle = gradient;
ctx.arc(p.location.x, p.location.y, p.radius, Math.PI*2, false);
ctx.fill();
}
} // end draw()
function updatePart()
{
for(var i = 0; i < particles.length; i++)
{
var p = particles[i];
p.remaining_life--;
p.radius--;
p.location.x += p.speed.x;
p.location.y += p.speed.y;
// Adjust the alpha/opacity setting based on age
// Opacity value is based on life of particle
// It goes to zero as the particle gets older
p.opacity = Math.round(p.remaining_life / p.life * 100) / 100;
// Remove the dead and immediately reincarnate -- make them reborn at new location
if (p.remaining_life < 0 || p.radius < 0)
{
// Recall p = particles[i]
particles[i] = new particle();
}
}
} // end updatePart
// Call update and draw -- at same rate <--- is this a good or bad idea? Why?
setInterval(updatePart, 33);
// For anything to happen must call draw (i.e. render something)
setInterval(draw, 33);
</script>