HTML5 Canvas Particle System (Pt. 2)

This is a Part 2 of a tutorial post HTML5 Canvas Particles System (Pt. 1). In this tutorial I will cover:

  1. Initialising Canvas (covered in Pt. 1)
  2. Drawing circles in Canvas
  3. Simple animation for particle movement using SetInterval()
  4. Improving the code using RequestAnimationFrame()

Drawing Circles in Canvas

Now that we have initialized a blank canvas, we can begin to draw in it. For our particle system we will need to draw the individual “particles”, each one will be drawn as a circle. In order to draw a circle in Canvas we need to first get the canvas element using GetElementById and set the width and height to the browser window, then specify that we are working in 2D using getContext:

1
2
3
4
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
c.width = window.innerWidth;
c.height = window.innerHeight;

We can now define our draw() function and then call it:

1
2
3
4
5
6
7
8
9
10
11
12
function draw() {
  x = 90; // x co-ordinate
  y = 70; // y co-ordinate
  r = 60; // radius of a circle
  c = "#ef5da1"; // fill colour

  ctx.beginPath();
  ctx.arc( x, y, r, 0, 6);
  ctx.fillStyle = c;
  ctx.fill();
}
draw();

And here is our first circle:

We can now create an array of particles for us to draw. First let’s initialise some variables that we will need available globally:

1
2
3
var ps = [];// initialize an empty particle array
var MAX_NUM = 500; // this is the maximum number of particles we want to generate and draw
var colors = [ '#69D2E7', '#A7DBD8', '#E0E4CC', '#F38630', '#FA6900', '#FF4E50', '#F9D423' ]; // this is an array of colour that the particles can be

In order to generate all particles we will create a spawn() function and call it. Here we are populating the empty ps[] array with randomly generated values, within the given bound (in this case the width and height of the browser window):

1
2
3
4
5
6
7
8
9
10
function spawn() {
  for(var i = 0; ps.length < MAX_NUM; i++) {
      ps[i] = { x: Math.random() * window.innerWidth,
                y: Math.random() * window.innerHeight,
                r: Math.random() * 5,
                c: colors[Math.floor(Math.random() * colors.length)]
              };
      }
}
spawn();

We now need to modify our draw() function to loop over all the particles and draw them:

1
2
3
4
5
6
7
8
9
function draw() {
  for(var i=0; i < ps.length; i++) {
  ctx.beginPath();
      ctx.arc( ps[i].x, ps[i].y, ps[i].r, 0, 6);
      ctx.fillStyle = ps[i].c;
      ctx.fill();
  }
}
draw();

If we run the code now, we should see something like this:

This is the full script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var ps = [];
var MAX_NUM = 500;
var colors = [ '#69D2E7', '#A7DBD8', '#E0E4CC', '#F38630', '#FA6900', '#FF4E50', '#F9D423' ];

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
c.width = window.innerWidth;
c.height = window.innerHeight;

// create the particles
function spawn() {
  for(var i=0; ps.length < MAX_NUM; i++) {
    ps[i] = { x: Math.random()*window.innerWidth,
              y: Math.random()*window.innerHeight,
              r: Math.random()*5,
              c: colors[Math.floor(Math.random()*colors.length)]
            };
   }
}
function draw() {
  for(var i=0; i < ps.length; i++) {
  ctx.beginPath();
      ctx.arc( ps[i].x, ps[i].y, ps[i].r, 0, 6);
      ctx.fillStyle = ps[i].c;
      ctx.fill();
  }
}
spawn();
draw();

Simple Animation Using SetInterval()

We are now ready to animate our particle system. We will need to write an update() function, which will update the x and y values of each particle on each frame. On each update we also need to reset the width and height to clear the canvas (Tip: remove this to see what happens if the canvas is not cleared):

1
2
3
4
5
6
7
8
function update() {
  c.width = window.innerWidth;
  c.height = window.innerHeight;
    for(var i=0; i < ps.length; i++) {
        ps[i].y += 1;
        ps[i].x += -1 + (Math.random() * 3);
    }
}

We can now call this function on each frame using SetInterval() and then draw the particles with the updated co-ordinates:

1
2
3
4
setInterval(function() {
  update();
  draw();
}, 30);

You may have noticed that the particles simply dissapear once they have reached the bottom of the brower window, if you would liek to animation to keep on running we could reset the x and y of each aprticles if it leaves the screen to start all over again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function reset() {
    //reset the x and y coordinates if a particle leaves the canvas
    for(var i=0; i < ps.length; i++) {
        //reset if y or coordinate has left the canvas
        if(ps[i].y > c.height) {
            ps[i].y = Math.random()*window.innerHeight;
            ps[i].color = colors[Math.floor(Math.random() * colors.length)];
        }
        //reset if x or coordinate has left the canvas
        if(ps[i].x > c.width || ps[i].x < 0){
          ps[i].x = Math.random()*window.innerWidth;
          ps[i].color = colors[Math.floor(Math.random() * colors.length)];
        }
    }
}

Now we have to call this new reset() function on each frame:

1
2
3
4
5
setInterval(function() {
  update();
  draw();
  reset();
}, 30);

Now you have a never ending animation of colourful particles slowly making their way down the screen. The full particles.js can be found here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// declare vars
var ps = [];
var MAX_NUM = 500;
var colors = [ '#69D2E7', '#A7DBD8', '#E0E4CC', '#F38630', '#FA6900', '#FF4E50', '#F9D423' ];

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
c.width = window.innerWidth;
c.height = window.innerHeight;

spawn();

//create the particles
function spawn() {
  for(var i=0; ps.length < MAX_NUM; i++) {
    ps[i] = { x: Math.random()*window.innerWidth,
              y: Math.random()*window.innerHeight,
              r: Math.random()*5,
              c: colors[Math.floor(Math.random()*colors.length)]
            };
   }
}

function update() {
    c.width = window.innerWidth;
    c.height = window.innerHeight;
    for(var i=0; i < ps.length; i++) {
        ps[i].y += 1 ;
        ps[i].x += -1 + (Math.random() * 3);
        //ps[i].r = Math.random()*5;
    }
}

function reset() {
    //reset the x and y coordinates if leaves the canvas
    for(var i=0; i < ps.length; i++) {
        //reset if y or coordinate has left the canvas
        if(ps[i].y > c.height) {
            ps[i].y = Math.random()*window.innerHeight;
            ps[i].color = colors[Math.floor(Math.random() * colors.length)];
        }
        //reset if x or coordinate has left the canvas
        if(ps[i].x > c.width || ps[i].x < 0){
          ps[i].x = Math.random()*window.innerWidth;
          ps[i].color = colors[Math.floor(Math.random() * colors.length)];
        }
    }
}

function draw() {
  for(var i=0; i < ps.length; i++) {
    ctx.beginPath();
      ctx.arc( ps[i].x, ps[i].y, ps[i].r, 0, 6);
      ctx.fillStyle = ps[i].c;
      ctx.fill();
  }
}

setInterval(function() {
  update();
  draw();
  reset();
}, 30);

Live demo here.

In Pt.3, I will cover how we can improve our animation using RequestAnimationFrame()(Pt.3 can be found here).