<canvas id="myCanvas"></canvas>
body {
background-color:rgba(0,0,0,0.1);
margin:0;
padding:0;
}
canvas {
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
}
#linkage {
position:fixed;
top:145px;
right:0px;
background-color:#2d2d3d;
color:#ffffff;
text-decoration:none;
padding:10px;
width:100px;
}
h1 {
text-align:center;
margin-top:50px;
}
h1, p, h2, a, h3 {
font-family:'arial';
color:#efefef;
}
p {
font-size:10pt;
font-style:italic;
color:#686868;
text-align:center;
}
#effectChanger {
position:fixed;
bottom:20px;
right:20px;
width:250px;
}
#effectChanger p {
text-align:left;
}
/*********************************************************************
* #### Simple Particle Engine v3.0 (Work in progress!) ####
* Coded by Jason Mayes 2014. www.jasonmayes.com
* Please keep this disclaimer with my code if you use it. Thanks. :-)
* Got feedback or questions, ask here:
* https://plus.google.com/+JasonMayes/posts
* Updates will be posted to this site:
* http://www.jasonmayes.com/projects/particleEngine/
* Or see Github:
* https://github.com/jasonmayes/Particle-Engine
*********************************************************************/
// Polyfill for requestAnimationFrame.
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
/* The magical particle engine! */
var jmParticleEngine = function() {
// Reference to the canvas DOM element.
var canvas = null;
// Hold canvas context.
var ctx = null;
// Hold all emitters.
var emitters = [];
/**
* A class which allows us to create Particle instances.
* @param {Number} x Starting x co-ordinate of the particle.
* @param {Number} y Starting y co-ordinate of the particle.
* @param {Number} w Width of particle.
* @param {Number} h Height of particle.
* @param {Number} rot Rotation in radians of the particle.
* @param {Number} xVel Velocity in the x direction.
* @param {Number} yVel Velocity in the y direction.
* @param {Number} life How long will the particle last for in animation frames.
* @param {Number} s How will particle size change with life? 0 = no change,
* 1 = smaller, 2 = larger. Taken as a % of width / height.
* @param {Number} r The amount of red in the particle (0 - 255).
* @param {Number} g The amount of green in the particle (0 - 255).
* @param {Number} b The amount of blue in the particle (0 - 255).
* @param {Number} i Optional. The index of a preloaded image we wish to
* render.
*/
function Particle(x, y, w, h, rot, xVel, yVel, life, s, r, g, b, i) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.rotation = rot;
this.xVelocity = xVel;
this.yVelocity = yVel;
this.maxLife = life;
this.life = life;
this.growthType = s;
this.r = r;
this.g = g;
this.b = b;
this.image = i;
}
/**
* A class which allows us to create Emitters of particles.
* @param {Number} x The x co-ordinate of the emitter location.
* @param {Number} y The y co-ordinate of the emitter location.
* @param {Number} m Enforce a hard limit on the number of displayable
* particles at any given time. Default is 500.
* @param {Function} p Particle generator function which returns a new particle.
* @param {CanvasRenderingContext2D=} ctx Optional. The canvas context we want to render to.
*/
function Emitter(x, y, m, p, ctx_) {
this.ctx = ctx || ctx_;
this.x = x;
this.y = y;
this.maxParticles = m || 500;
// Array to hold all particles we have generated for this emitter.
this.particles = [];
this.generateParticle = p;
this.emit = false;
this.images = [];
}
Emitter.prototype.start = function() {
this.emit = true;
};
Emitter.prototype.stop = function() {
this.emit = false;
};
Emitter.prototype.fetchCtx = function() {
return this.ctx;
};
Emitter.prototype.preloadImage = function(url) {
var img = new Image();
var that = this;
var id = this.images.length;
that.images.push({"image": img, "loaded": false});
this.attachHandler(img, 'load', function() {
that.images[id].loaded = true;
});
img.crossOrigin = '';
img.src = url;
return id;
};
Emitter.prototype.attachHandler = attachHandler_;
/**
* Cross browser attach event handler.
* @param {String | Object} elementId ID of the DOM element we wish to attach
* event to. Alternatively can pass the object itself.
* @param {String} event The type of event to listen for eg 'click'.
* @param {Function} functionName The function to be attached to the event.
*/
function attachHandler_(elementId, event, functionName) {
var element = {};
if (typeof elementId === 'string') {
element = document.getElementById(elementId);
} else {
element = elementId;
}
if (element.addEventListener) {
element.addEventListener(event, functionName, false);
} else if (element.attachEvent) {
element.attachEvent('on' + event, functionName);
} else {
element['on' + event] = functionName;
}
}
/**
* Generate a random number from 0 to (n - s).
* @param {Number} n The highest number we wish to generate numbers to.
* @param {Number} s Optional: A number to be subtracted from the resulting
* random number. This allows us to generate negative numbers.
* @param {Boolean} r If true, rounds the generated number to whole number.
* Else it is a double.
*/
function randomNumber_(n, s, r) {
if (n === undefined) {
n = 1;
}
if (s === undefined) {
s = 0;
}
if (r === undefined) {
r = true;
}
if (r) {
return Math.round((Math.random() * n) - s);
} else {
return (Math.random() * n) - s;
}
}
/**
* A function to actually draw the particle to the screen once the context
* and other parameters have been set up.
* @param {Particle} particle The particle we wish to draw.
* @param {Number} width The calculated width of the particle at a given point in life.
* @param {Number} height The calculated height of the particle at a given point in life.
* @param {Image} image Optional. The image we wish to draw as the particle. If no image specified we draw a rectangle instead.
*/
function renderParticle(canvasCtx, particle, width, height, image) {
// Only rotate if rotation is applied.
if (particle.rotation !== 0) {
// Save context so we can revert to it later.
canvasCtx.save();
// Move to where we want to draw our image.
canvasCtx.translate(particle.x, particle.y);
// Rotate around that point.
canvasCtx.rotate(particle.rotation);
if (image !== undefined) {
// Draw the image about its centre.
canvasCtx.drawImage(image, -width / 2, -height / 2, width, height);
} else {
// Draw rectangle.
canvasCtx.fillRect(-width / 2, -height / 2, width, height);
}
// Restore context to how it was before.
canvasCtx.restore();
} else {
if (image !== undefined) {
// Draw the image about its centre.
canvasCtx.drawImage(image, particle.x, particle.y, width, height);
} else {
// Draw rectangle.
canvasCtx.fillRect(particle.x, particle.y, width, height);
}
}
}
/**
* Our main animation loop to update each particle's state and redraw the scene.
* @param {Number} time The epoch time passed by the browser from the
* requestAnimationFrame request.
*/
function particleLoop(time) {
// Now lets iterate through all emitters.
var n = emitters.length;
// Avoid re-declaring width and height tmp vars.
var width = 0;
var height = 0;
while (n--) {
// Clear the canvas.
emitters[n].fetchCtx().clearRect(0, 0, canvas.width, canvas.height);
// Ensure we uphold maximum particle count.
if (emitters[n].particles.length === emitters[n].maxParticles) {
emitters[n].particles.shift();
}
// Only generate new particles if emitter is turned on.
if (emitters[n].emit) {
// Generate a new particle and push it to our particles array.
var diff = emitters[n].maxParticles - emitters[n].particles.length;
var wanted = Math.ceil(diff * 0.01);
if (diff > 0) {
var j = 0;
while (j < wanted) {
emitters[n].particles.push(emitters[n].generateParticle());
j++;
}
}
}
// Now lets iterate through all particles and draw them all in the next time step.
var i = emitters[n].particles.length;
while (i--) {
// Check to see if particle has died.
if (emitters[n].particles[i].life > 0) {
// Define how particle size changes with life.
if (emitters[n].particles[i].growthType === 1) {
// Decrease size with life of particle.
width = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife) * emitters[n].particles[i].width;
height = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife) * emitters[n].particles[i].height;
} else if (emitters[n].particles[i].growthType === 2) {
// Increase size with life of particle.
width = (1 - (emitters[n].particles[i].life / emitters[n].particles[i].maxLife)) * emitters[n].particles[i].width;
height = (1 - (emitters[n].particles[i].life / emitters[n].particles[i].maxLife)) * emitters[n].particles[i].height;
} else {
// No effect with life - same size throughout.
width = emitters[n].particles[i].width;
height = emitters[n].particles[i].height;
}
// If we are not trying to render an image particle then draw rectangle.
if (emitters[n].particles[i].image === undefined) {
emitters[n].fetchCtx().fillStyle = 'rgba(' + emitters[n].particles[i].r + ',' + emitters[n].particles[i].g + ',' + emitters[n].particles[i].b + ', 1)';
emitters[n].fetchCtx().globalAlpha = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife);
// Render particle as rectangle.
renderParticle(emitters[n].fetchCtx(), emitters[n].particles[i], width, height);
} else {
// Otherwise lets ensure image has been defined and loaded and draw it.
if (emitters[n].images[emitters[n].particles[i].image] !== undefined) {
if (emitters[n].images[emitters[n].particles[i].image].loaded) {
emitters[n].fetchCtx().globalAlpha = (emitters[n].particles[i].life / emitters[n].particles[i].maxLife);
// Render particle as image.
renderParticle(emitters[n].fetchCtx(), emitters[n].particles[i], width, height, emitters[n].images[emitters[n].particles[i].image].image);
}
}
}
// Update particle properties for next cycle.
emitters[n].particles[i].x += emitters[n].particles[i].xVelocity;
emitters[n].particles[i].y += emitters[n].particles[i].yVelocity;
emitters[n].particles[i].life--;
} else {
// If dead, remove it.
emitters[n].particles.splice(i, 1);
}
}
}
requestAnimationFrame(particleLoop, canvas);
}
// Publicly accessible functions exposed to users.
return {
/**
* Initiate and run the particle engine.
* @param {String} canvasId The DOM id of the canvas element we shall be rendering to.
* @param {Number} width Width of canvas.
* @param {Number} height Height of canvas.
*/
init: function(canvasId, width, height) {
canvas = document.getElementById(canvasId);
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d');
particleLoop();
},
/**
* Allow external users to generate new emitters.
* @param {Number} x The x co-ordinate of the emitter location.
* @param {Number} y The y co-ordinate of the emitter location.
* @param {Number} m Enforce a hard limit on the number of displayable
* particles at any given time. Default is 500.
* @param {Function} p Particle generator function to use which returns new particles.
* @param {CanvasRenderingContext2D=} ctx Optional. The canvas context we want to render to.
*/
generateEmitter: function(x, y, m, p, c) {
return new Emitter(x, y, m, p, c);
},
/**
* Allow external users to generate new particles.
* @param {Number} x Starting x co-ordinate of the particle.
* @param {Number} y Starting y co-ordinate of the particle.
* @param {Number} w Width of particle.
* @param {Number} h Height of particle.
* @param {Number} rot Rotation in radians of the particle.
* @param {Number} xVel Velocity in the x direction.
* @param {Number} yVel Velocity in the y direction.
* @param {Number} life How long will the particle last for in animation frames.
* @param {Number} s How will particle size change with life? 0 = no change,
* 1 = smaller, 2 = larger. Taken as a % of width / height.
* @param {Number} r The amount of red in the particle (0 - 255).
* @param {Number} g The amount of green in the particle (0 - 255).
* @param {Number} b The amount of blue in the particle (0 - 255).
* @param {Number} i Optional. The index of a preloaded image we wish to
* render.
*/
generateParticle: function(x, y, w, h, rot, xVel, yVel, life, s, r, g, b, i) {
return new Particle(x, y, w, h, rot, xVel, yVel, life, s, r, g, b, i);
},
/**
* Add an emitter to the engine and start it.
* @param {jmParticleEngine.Emitter} e The emitter we wish to add to the engine.
*/
addEmitter: function(e, s) {
if (s !== undefined && s) {
e.start();
}
emitters.push(e);
},
/**
* Publicly expose the randomNumber generator.
* @param {Number} n The highest number we wish to generate numbers to.
* @param {Number} s Optional: A number to be subtracted from the resulting random number.
* This allows us to generate negative numbers.
* @param {Boolean} r If true, rounds the generated number to whole number.
* Else it is a double.
*/
randomNumber: randomNumber_,
/**
* Publicly expose cross browser attach event handler.
* @param {String} elementId ID of the DOM element we wish to attach event to.
* @param {String} event The type of event to listen for eg 'click'.
* @param {Function} functionName The function to be attached to the event.
*/
attachHandler: attachHandler_
};
}();
/* Lets use this sweet engine! Example usage below! */
// Initiate engine to draw to DOM canvas with id "myCanvas" and detail its dimensions.
jmParticleEngine.init('myCanvas', window.innerWidth, window.innerHeight);
// Define a particle generators - each generates particles of one particle type.
function particleGenerator1() {
var size = jmParticleEngine.randomNumber(2, undefined, true);
// Note context of this is bound to the emitter calling the function,
// so we can simply grab the emitter's x and y for its starting point.
return jmParticleEngine.generateParticle(
// Start at the emitter's x co-ordinate.
this.x,
// Start at the emitter's y co-ordinate.
this.y,
// Width.
size,
// Height.
size,
// Rotation.
0,
// xVelocity.
jmParticleEngine.randomNumber(15, 7.5, false),
// yVelocity.
jmParticleEngine.randomNumber(15, 7.5, false),
// Life.
120,
// How will particle change size vs life.
// 0 - no change, same size always.
// 1 - smaller with age.
// 2 - larger with age.
0,
// Red.
jmParticleEngine.randomNumber(191, -64, true),
// Green.
jmParticleEngine.randomNumber(191, -64, true),
// Blue.
jmParticleEngine.randomNumber(191, -64, true)
);
}
function particleGenerator2() {
var size = jmParticleEngine.randomNumber(20, undefined, true);
// Note context of this is bound to the emitter calling the function,
// so we can simply grab the emitter's x and y for its starting point.
return jmParticleEngine.generateParticle(
// Start at the emitter's x co-ordinate.
this.x,
// Start at the emitter's y co-ordinate.
this.y,
// Width.
size,
// Height.
size,
// Rotation.
0,
// xVelocity.
jmParticleEngine.randomNumber(15, 7.5, false),
// yVelocity.
jmParticleEngine.randomNumber(15, 7.5, false),
// Life.
64,
// How will particle change size vs life.
// 0 - no change, same size always.
// 1 - smaller with age.
// 2 - larger with age.
0,
// Red.
jmParticleEngine.randomNumber(255, 0, true),
// Green.
jmParticleEngine.randomNumber(64, 0, true),
// Blue.
jmParticleEngine.randomNumber(32, 0, true)
);
}
function particleGenerator3() {
var size1 = jmParticleEngine.randomNumber(3, undefined, true);
var size2 = jmParticleEngine.randomNumber(100, undefined, true);
// Note context of this is bound to the emitter calling the function,
// so we can simply grab the emitter's x and y for its starting point.
return jmParticleEngine.generateParticle(
// Start at the emitter's x co-ordinate.
this.x + jmParticleEngine.randomNumber(1000, 500, false),
// Start at the emitter's y co-ordinate.
this.y,
// Width.
size1,
// Height.
size2,
// Rotation.
0,
// xVelocity.
jmParticleEngine.randomNumber(1, 1, false),
// yVelocity.
jmParticleEngine.randomNumber(30, 0, false),
// Life.
60,
// How will particle change size vs life.
// 0 - no change, same size always.
// 1 - smaller with age.
// 2 - larger with age.
1,
// Red.
jmParticleEngine.randomNumber(32, 0, true),
// Green.
jmParticleEngine.randomNumber(50, 0, true),
// Blue.
jmParticleEngine.randomNumber(255, 0, true)
);
}
function particleGenerator4() {
var size = jmParticleEngine.randomNumber(128, undefined, true);
var grey = jmParticleEngine.randomNumber(255, 0, true);
// Note context of this is bound to the emitter calling the function,
// so we can simply grab the emitter's x and y for its starting point.
return jmParticleEngine.generateParticle(
// Start at the emitter's x co-ordinate.
this.x,
// Start at the emitter's y co-ordinate.
this.y,
// Width.
size,
// Height.
size,
// Rotation.
0,
// xVelocity.
jmParticleEngine.randomNumber(15, 7.5, false),
// yVelocity.
jmParticleEngine.randomNumber(15, 7.5, false),
// Life.
42,
// How will particle change size vs life.
// 0 - no change, same size always.
// 1 - smaller with age.
// 2 - larger with age.
0,
// Red.
grey,
// Green.
grey,
// Blue.
grey
);
}
function particleGenerator5() {
var size = jmParticleEngine.randomNumber(128, -64, true);
// Note context of this is bound to the emitter calling the function,
// so we can simply grab the emitter's x and y for its starting point.
return jmParticleEngine.generateParticle(
// Start at the emitter's x co-ordinate.
this.x ,
// Start at the emitter's y co-ordinate.
this.y ,
// Width.
size,
// Height.
size,
// Rotation.
jmParticleEngine.randomNumber(Math.PI * 2, 0, false),
// xVelocity.
jmParticleEngine.randomNumber(18, 9, false),
// yVelocity.
jmParticleEngine.randomNumber(18, 9, false),
// Life.
64,
// How will particle change size vs life.
// 0 - no change, same size always.
// 1 - smaller with age.
// 2 - larger with age.
0,
// Red.
0,
// Green.
0,
// Blue.
0,
// If we wish to use a preloaded image, specify index here.
0
);
}
// Generate emitters using the particle generator function defined above.
var emit1 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 1500, particleGenerator5);
emit1.preloadImage('https://storage.googleapis.com/jm-cors/images/fire2.png');
// We can also pass custom Canvas Context if we want to draw to a different canvas.
const customContext = document.getElementById('myCanvas').getContext('2d');
var emit2 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator2, customContext);
var emit3 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator3);
var emit4 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator4);
var emit5 = jmParticleEngine.generateEmitter(Math.ceil(window.innerWidth / 4), Math.ceil(window.innerHeight / 2), 5000, particleGenerator1);
var emitTmp = jmParticleEngine.generateEmitter(Math.ceil((window.innerWidth / 4) * 3), Math.ceil(window.innerHeight / 2), 750, particleGenerator1);
// Add emitters to engine! Ensure emit1 and emitTmp start
// straight away.
jmParticleEngine.addEmitter(emit1, true);
jmParticleEngine.addEmitter(emit2);
jmParticleEngine.addEmitter(emit3);
jmParticleEngine.addEmitter(emit4);
jmParticleEngine.addEmitter(emit5);
jmParticleEngine.addEmitter(emitTmp, true);
// Attach emitters 1, 2, 3 and 4 to mouse position.
jmParticleEngine.attachHandler('myCanvas', 'mousemove', function(e){
if (!e) {
e = window.event;
}
emit1.x = e.clientX;
emit1.y = e.clientY;
emit2.x = e.clientX;
emit2.y = e.clientY;
emit3.x = e.clientX;
emit3.y = e.clientY;
emit4.x = e.clientX;
emit4.y = e.clientY;
emit5.x = e.clientX;
emit5.y = e.clientY;
});
// Stop the second emitter after 5 seconds.
setTimeout(function(){
emitTmp.stop();
}, 5000);
// Event listener for changing particle effect dropdown.
jmParticleEngine.attachHandler('changer', 'change', function(e){
if (!e) {
e = window.event;
}
var sel = document.getElementById('changer');
var val = sel[sel.selectedIndex].value;
emit1.stop();
emit2.stop();
emit3.stop();
emit4.stop();
emit5.stop();
if (val === '1') {
emit1.start();
} else if (val === '2') {
emit2.start();
} else if (val === '3') {
emit3.start();
} else if (val === '4') {
emit4.start();
} else if (val === '5') {
emit5.start();
}
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.