Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Generating a Particle System with JavaScript

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

If you've ever seen a fireworks effect, created in Flash, I'll show you how to reproduce the effect using pure JavaScript! Why let the Flash developers have all the fun?


Setting Up your Workspace

Requirements

Knowledge of Javascript Object Notation. If you could use some training in this area, no problem, Leigh Kaszick has an in depth tutorial on The Basics of Object-Oriented JavaScript.

Basic knowledge of Raphael.js. Once again, if you're new to it, then you might want to check An Introduction to the Raphael JS Library by Damian Dawber.


What is a Particle System?

A particle system, in terms of computer graphics, relates to replicating the behavior of a natural particle systems in a 3D model such as explosions, fire, clouds, and a plethora of other phenomena which are really difficult to implement using conventional programming techniques.


A Basic particle system consists of -

  • An Emitter - The point in the space where the particles originated.
  • The Stage - The place which holds the Particle System.
  • Forces - These are external forces which influence the movement of the particles. In our tutorial, it will be gravity.

Step 1

We will begin by creating our particle image.

Open Photoshop and create a 25px by 25px canvas.


Create a new layer.


Select a 23px round brush with hardness 100%. In this tutorial we are using the white color, but you can choose any brush shape or color as you like.


Now we must hide the background layer, and then, using the save for web and devices option, save it as "particle_img" in png-24 format.


Step 2

Now, let's get started with the coding. To begin, paste in the following code.

 
<html> 
<head> 
<script type="text/JavaScript" src="raphael-min.js"></script> 
<script type="text/JavaScript" src="sps.js"></script> 
<title>Simple Particle System</title> 
</head> 
<body style="background:#000000;color:#CCCCCC" onload="startSystem()"> 
<div id="pane" ></div> 
</body> 
</html>

This is a simple snippet of HTML code which consists of:

  • Importing raphael.js and sps.js, in which we will work with the JavaScript code of our Particle System.
  • Then, in the body, there is a div with an ID of pane - it will be our stage.
  • The body tag also consists of a startSystem() function which will be called on the onload event. We'll define it later in our JavaScript file.

Step 3

Next, we are going to create our sps.js, in which we will type the actual code for our Particle System.


Step 4

Since we are working with a 2D model, in order to avoid a bunch of x,y variables in the script, we will create a Vector class that will take care of the coordinates for us.

 
function Vector() 
{ 
	this.x =0; 
	this.y =0; 
    //this.z =0;    
	 
}

Note: We won't be using Z coordinates since we are not concerned with the particle distance from the camera view(eyes), Z coordinate mainly deals with scale and opacity attributes.


Step 5

We'll be needing functions for setting the values of the variables and adding them, so we will declare the following functions in our Vector class.

 
function Vector() 
{ 
	this.x =0; 
	this.y =0; 
    //this.z =0;    
	this.set = function(X,Y) // setting the values of X,Y to our object 
	{ 
		with(this) 
		{ 
			x = X; 
			y =Y; 
			//z = Z; 
		} 
	} 
	this.add = function(vector2) // adding another vector's values to our current vector object 
	{ 
		this.x = this.x + vector2.x; 
		this.y = this.y + vector2.y; 
		//this.z = this.z + vector2.z;	 
	} 
}

Note:Be sure to add Z in the set function parameters, if you are using it.


Step 6

Let's create a Vector object that will hold the coordinates where the particles originate; also, we will declare a global Raphael object and a timer variable which we will be using later.

 
var canvas;  // our raphael object 
var timer;  
var emitter = new Vector(); 
emitter.set(400,200);

Step 7

We need to create a class for our particle objects, it will consist of basic properties such as:

  • Color and shape - basic properties of the particle.
  • Size - the size of the particle.
  • Location - its point of origin.
  • Velocity - the speed (here, it will refer to the spread of the particles on the stage).
  • Acceleration - increase in speed.
  • Lifespan - The life of the particle.

In the JavaScript world, we will slightly change a few variables, such as the color and size that were already defined when we created the image in Photoshop.

 
function Particle() 
{ 
this.size = Math.random() * 10 + 15;  
this.particle =  canvas.image("particle_img.png",emitter.x,emitter.y,this.size,this.size); 
this.loc = new Vector(); 
this.vel = new Vector(); 
this.acc = new Vector(); 
this.lifespan = Math.random() * 250; 
}

In JavaScript, classes are declared using the function and its variables are declared using this.

  • We want the size of the particles to be randomly generated, and between 15 to 25.
  • Particle is the Raphael image object with image name, coordinates, and size, as parameters.
  • loc is the location of the particle.
  • acc is the acceleration.
  • vel is the velocity.
  • lifespan is the period in which the particle lives on the stage.

Step 8

We will need to create functions for the following conditions:

  • Creating a particle on the canvas with the above declared properties.
  • Constantly updating its position with respect to its acceleration.
  • Checking if the particle is dead, then we have to remove it from our canvas, else our stage will be full of dead particles :).

Step 9

First, we will declare the init function, which we will use to initialize our particles.

 
 this.init = function(){ 
	with(this) 
	{ 
		particle.rotate(Math.random()*360);  
		acc.set(0,0.05); 
		vel.set(Math.random() * 4 -2,Math.random() * 3 -1); 
		loc.set(emitter.x,emitter.y);	 
	}		 
}

Here we have wrapped the code inside the with(this) which refers to the current particle object.

  • particle.rotate() is used to rotate the object randomly between 0 to 360 degrees. In this case, it does not matter since we are using a round object, however, with other objects, such as stars, it does matter, since it looks odd that all the particles have same geometrical angles.
  • We will set the initial acceleration to 0,0.5, because we want to illustrate how the particles are bound to gravity; that's why we have initialized the y coordinate. As we increase the value of the acceleration, the faster particles' speed increases.
  • Here, vel refers to the spread of the particles on the stage - that is Math.random() * 4 -2 will generated numbers in the range -2 to 2.
  • loc takes the initial values of the particle.

A few points should be noted:

  1. Condition for Acceleration
    • Positive values increase acceleration.
    • For a gravity free stage, acceleration = 0.
    • For inverse gravity, negative values should be used.
  2. Conditions for vel
    • For increasing the spread, we will use a greater value of the range generated from Math.random().
    • For emitting particles in a particular side, we can achieve it either by multiplying by the maximum positive or a negative number.


Step 10

We will now create a function to update the particle's values to its new position.

 
this.update = function(){ 
	with(this) 
	{ 
		vel.add(acc); 
		loc.add(vel); 
		lifespan -= 1; 
		particle.animate({x:loc.x,y:loc.y},39); 
	}	 
}

Here, whenever the update function is called, acceleration is added to the vel, and that is added to the location. Lastly, the particle is updated to its new location. When constant acceleration is added, we'll give the impression that the particle is under some force, since it has to cover more distant per unit time. Also, whenever update is called, its lifespan is decreased by 1.


Step 11

Finally, for the particle class, we are going to create a function to check whether a particle's lifespan has ended or not.

 
this.dead = function() 
	{ 
		if(this.lifespan<0) 
		{ 
			return true; 
		} 
		else  
		{ 
			return false; 
		} 
	}

Step 12

Our Particle class looks like so:

 
function Particle() 
{ 
	 
	this.size = Math.random() * 10 + 15;  
	this.particle =  r.image("particle_img.png",emitter.x,emitter.y,this.size,this.size); 
	this.loc = new Vector(); 
    this.vel = new Vector(); 
    this.acc = new Vector(); 
    this.lifespan = Math.random() * 250; 
    this.init = function(){ 
		with(this) 
		{ 
			particle.rotate(Math.random()*360); 
			acc.set(0,0.05); 
			vel.set(Math.random() * 4 -2,Math.random() * 3 -1); 
			loc.set(emitter.x,emitter.y); 
			 
		} 
		 
		} 
	this.update = function(){ 
		with(this) 
		{ 
			vel.add(acc); 
			loc.add(vel); 
			lifespan -= 1; 
			particle.animate({x:loc.x,y:loc.y},39); 
		} 
		 
		} 
		 
	this.dead = function() 
	{ 
		if(this.lifespan<0) 
		{ 
			return true; 
		} 
		else  
		{ 
			return false; 
		} 
	} 
	 
}

Step 13

Now we will create a particle system class. Let's start by declaring the member variable.

 
this.particles = new Array();

Here, particles is an array of the Particles objects that we'll soon create.


Step 14

Now we need to create the functions for initializing and running our particle system; so we begin by creating our init function.

 
this.init = function(num) 
	{ 
		 for (var i = 0; i < num; i++)  
		 { 
          this.particles[i] = new Particle(); 
		  this.particles[i].init(); 
		 } 
		 
	}

First, we take the number of particles to be created as function parameters. Then, using the for loop, we create the particle objects and initialize them by calling their init function.


Step 15

Now, we will create a run function that will run our particle system.

 
this.run = function(){ 
		with(this){ 
					timer = setInterval(function(){ 
					for (i=particles.length -1; i>=0; i--)  
					{ 
 		      			if(particles[i].dead()) { 
        					particles[particles.length -1].particle.remove(); 
							particles.pop(); 
							var temp = new Particle(); 
				 			temp.init(); 
				 			particles.unshift(temp);  
							} 
						particles[i].update(); 
			   		} 
				 },40); 
	 
			} 
	   
		}

Here our code is wrapped up inside the with(this) block, which refers to the current particle system object. Now since our particles need to be constantly updated to the next location, we call the particle's update() function inside a setInterval() function which executes the function in a defined interval of time. We update all the particles positioning by iterating through the particles array.

Now we also want to check if the particle is dead or not. We accomplish this task by calling the particle's dead() function. If a particle is dead, then we do the following:

  • Remove it from our array.
  • shift elements back by one, starting from the position after the dead particle's index.
  • Push a new particle to the end of the array.
 
var p = particles[i]; 
	for(var j=i;j<particles.length-1;j++) 
	{ 
 		particles[j] = particles[j+1]; 
	} 
p.particle.remove(); 
var temp = new Particle(); 
temp.init(); 
particles.push(temp);

The above code is the optimal solution, but when we implement that in the JavasSript world, the emission is so spontaneous that the engines hangs out. That's why we have used the alternate code to increase the complexity and render a smoother animation. You can try it out yourself ;).

Since the time period is in order of milliseconds, that's why in-spite of using a more complex algorythm, we get a spontaneous emission.


Step 16

At this point, our ParticleSystem class looks like so.

 
function ParticleSystem() 
{ 
	this.particles = new Array(); 
	this.init = function(num) 
	{ 
		 
		 
		 for (var i = 0; i < num; i++)  
		 { 
          this.particles[i] = new Particle(); 
		  this.particles[i].init(); 
		 } 
		  
		 
	} 
	 
 
	this.run = function(){ 
		with(this){ 
	timer = setInterval(function(){ 
				for (i=particles.length -1; i>=0; i--)  
			{ 
 		      if(particles[i].dead()) { 
        		 
				particles[particles.length -1].particle.remove(); 
				 particles.pop(); 
				var temp = new Particle(); 
				 temp.init(); 
				 particles.unshift(temp);  
							 
			} 
			 
			particles[i].update(); 
			 
    		} 
				 },40); 
	 
		} 
	   
		}		 
}

Step 17

Now we will declare our startSystem() function that will initiate the particle system.

 
startSystem = function(){ 
W = 800; H = 400; 
canvas = Raphael("pane", W, H); 
var ps = new ParticleSystem(); 
var iniVec = new Vector(); 
iniVec.set(emitter.x,emitter.y); 
ps.init(20,iniVec); 
 ps.run(); 
}

We initialize our raphael object with stage ID, width and height. Then, we create our particle system object and create a vector object with the origin coordinates. Then we are initializing our particle system by calling init, which in turn, initializes the particles. We also provide 20 as the number of particles in the parameters.

The number of particles must be between 20 to 30 for a smooth animation effect. Supplying a higher number will crash the browser or hang it!


Step 18

Here is what our sps.js file's code looks like at this point.

 
 
function Vector() 
{ 
	this.x =0; 
	this.y =0; 
    //this.z =0;    
	this.set = function(X,Y) 
	{ 
		with(this) 
		{ 
			x = X; 
			y =Y; 
			//z = Z; 
		 
		} 
	} 
	this.add = function(vector2) 
	{ 
		 
		 
		 
		this.x = this.x + vector2.x; 
		this.y = this.y + vector2.y; 
		//this.z = this.z + vector2.z; 
		 
		 
	} 
} 
 
 
var canvas;  // our raphael object 
var timer;  
var emitter = new Vector(); 
emitter.set(400,200); 
 
function Particle() 
{ 
	 
	this.size = Math.random() * 10 + 15;  
	this.particle =  canvas.image("img1.png",emitter.x,emitter.y,this.size,this.size); 
	this.loc = new Vector(); 
    this.vel = new Vector(); 
    this.acc = new Vector(); 
    this.lifespan = Math.random() * 250; 
    this.init = function(){ 
		with(this) 
		{ 
			particle.rotate(Math.random()*360); 
			acc.set(0,0.05); 
			vel.set(Math.random() * 4 -2,Math.random() * 3 -1); 
			loc.set(emitter.x,emitter.y); 
			 
		} 
		 
		} 
	this.update = function(){ 
		with(this) 
		{ 
			vel.add(acc); 
			loc.add(vel); 
			lifespan -= 1; 
			particle.animate({x:loc.x,y:loc.y},39); 
		} 
		 
		} 
		 
	this.dead = function() 
	{ 
		if(this.lifespan<0) 
		{ 
			return true; 
		} 
		else  
		{ 
			return false; 
		} 
	} 
	 
} 
 
 
function ParticleSystem() 
{ 
	this.particles = new Array(); 
	this.init = function(num) 
	{ 
		 
		 
		 for (var i = 0; i<num; i++)  
		 { 
          this.particles[i] = new Particle(); 
		  this.particles[i].init(); 
		 } 
		  
		 
	} 
	 
 
	this.run = function(){ 
		with(this){ 
	timer = setInterval(function(){ 
				for (i=particles.length -1; i>=0; i--)  
			{ 
 		      if(particles[i].dead()) { 
        		 
				particles[particles.length -1].particle.remove(); 
				 particles.pop(); 
				var temp = new Particle(); 
				 temp.init(); 
				 particles.unshift(temp);  
							 
			} 
			 
			particles[i].update(); 
			 
    		} 
				 },40); 
	 
		} 
	   
		} 
				 
} 
 
startSystem = function(){ 
W = 800; H = 400; 
  canvas = Raphael("pane", W, H); 
  var ps = new ParticleSystem(); 
	ps.init(20); 
	 ps.run(); 
       
}

Finally, we are done. This little project is compatible across all browsers, even in Internet Explorer, though IE6 does have some transparency issues. This particle system can be used practically anywhere you like, including backgrounds, explosion effects, logo and header intros, etc. I have produced three demos, which are modified versions of the code above. One of them implements different particle systems, such as smoke,dust and the bokeh particle system. Enjoy, and thanks for reading!