kirupa.com - Animating Many Things on a Canvas 


Active Discussions

Animating Many Things on a Canvas

by kirupa   |   28 June 2011

  Have questions? Discuss this HTML5 tutorial with others on the forums.

One of my favorite hobbies is to write small applications that move geometric shapes around in clever / interesting ways. Below is a short video highlighting what you will be creating:

[ click here for a live / running example ]

You can see a live example in a new window by clicking here. The circles in the new window are drawn and kept moving in a circular path by JavaScript, and they are drawn inside a Canvas object. In this article, we will be deconstructing how to go about creating all of this. Keep the example page open and go to View Source to see the code that is currently being used to do all of this.

Deconstructing the Example
In this article, we'll be deconstructing the code and seeing how everything ties together. Keep your source code displayed so you can follow along with the code that I am describing.

Defining the Canvas
Let's start with the easy part - defining your Canvas object in HTML:

<div id="container">
   <canvas id="myCanvas" width="500" height="500">
  
   </canvas>
</div>

In this section, all I am doing is creating a canvas object that lives inside a div element. The look of the canvas is defined by my single CSS rule whose selector matches the canvas's id of myCanvas:

<style>
 
#myCanvas
{
border-width: 1px;
border-style: dotted;
border-color: Gray;
}
 
</style>

There is nothing interesting going on with the style, so let's just start getting into the code itself...which provides plenty of entertainment.

Overview of our Code
As you can see, the bulk of what we are doing lives in JavaScript. Everything from drawing the circles to moving them around is handled entirely by our code which you can see below:

<script>
  
var mainCanvas = document.getElementById("myCanvas");
var mainContext = mainCanvas.getContext('2d');
 
var circles = new Array();
 
function Circle(context, radius, speed, width, xPos, yPos) {
 
this.context = context;
this.radius = radius;
this.speed = speed;
this.width = width;
this.xPos = xPos;
this.yPos = yPos;
 
this.counter = 0;
 
this.sign = Math.floor(Math.random()*2);
 
if (this.sign == 1)
{
this.sign = -1;
}
else
{
this.sign = 1;
}
}
 
Circle.prototype.update = function() {
 
this.counter += this.sign*this.speed;
 
this.context.beginPath();
this.context.arc(this.xPos + Math.cos(this.counter / 100) * this.radius, this.yPos + Math.sin(this.counter / 100) * this.radius, this.width, 0, Math.PI * 2, false);
this.context.closePath();
 
this.context.fillStyle = 'rgba(185, 211, 238, 0.5)';
this.context.fill();
};
 
function drawCircles() {
 
for (var i = 0; i < 100; i++) {
 
var randomX = Math.round(-200 + Math.random() * 700);
var randomY = Math.round(-200 + Math.random() * 700);
var speed = .2+Math.random()*3;
var size = 5+Math.random()*100;
 
var circle = new Circle(mainContext, 100, speed, size, randomX, randomY, 0);
circles.push(circle);
 
}
setInterval(draw, 20);
}
drawCircles();
 
function draw() {
mainContext.clearRect(0, 0, 500, 500);
 
for (var i = 0; i < circles.length; i++) {
 
var myCircle = circles[i];
myCircle.update();
}
 
}
 
</script>

Before diving into the code and learning what makes the circles move around, let me first describe what the code does so that you will be better prepared for what you will see.

Like I mentioned earlier, all of the JavaScript you see is responsible for drawing the circles into the canvas and animating them. You may be wondering if it really takes that much code to do something like this. The answer is "yes", and the reason for the verbosity is because Canvas is very basic. It is essentially a drawing surface. That's it. If you want to draw something, you define every single detail using code. If you want to display and animate what you created in code, you have to explicitly tell your canvas to redraw each frame. This sounds painful, but it really isn't too bad once you get familiar with it.

The basic steps for what we do is:

  1. Get a reference to our Canvas object so that we can do stuff to it.
  2. Create all of our Circle objects, define various properties on them, and call a method on that object to draw itself.
  3. Start a timer that takes each of our Circle objects and moves them around.
  4. Where there is a Circle object, there is a class file called Circle that goes with it. This means, there are two additional things that we do:
        
    1. Define a Circle class that helps store any details about its speed, size, initial position, etc.
    2. Add a function to our Circle class that is responsible for updating the circle's position and drawing itself.

I am taking really broad brush strokes in describing what our code does and omitting important details. Don't worry! You'll see those details elaborated on shortly.

Refererencing our Canvas Object
Let's go through our steps starting at the top. The first lines of code allow us to gain access to our Canvas and its API via the context object:

var mainCanvas = document.getElementById("myCanvas");
var mainContext = mainCanvas.getContext('2d');

The first line allows us to access our canvas object by using getElementById and passing in our canvas's ID value - myCanvas.

Our canvas object isn't particularly interesting for what we are trying to do. You first need to access an API that allows you to draw inside it. You do that by calling getContext on your canvas object and passing in the argument of the API you want to use. In this case, we pass in the 2d argument which returns an object that contains the APIs we will need to draw two-dimensional visuals!

Declaring an Array to Store our Circles
Next, let's look at our basic data structure for actually storing our circles:

var circles = new Array();

We basically use an array aptly called circles. You will see in a little bit how it is used.

Setting Up the Circles
We are finally getting to the functions now! The setupCircles function is responsible for creating the individual Circle objects:

function setupCircles() {
  
for (var i = 0; i < 100; i++) {
  
var randomX = Math.round(-200 + Math.random() * 700);
var randomY = Math.round(-200 + Math.random() * 700);
var speed = .2+Math.random()*3;
var size = 5+Math.random()*100;
 
var circle = new Circle(mainContext, 100, speed, size, randomX, randomY, 0);
circles.push(circle);
  
}
setInterval(draw, 20);
}
setupCircles();

This code is responsible for creating each individual circle; giving each circle a random starting position, speed, and size; and starting the timer that will help animate each circle.

Let's look at creating each circle first:

for (var i = 0; i < 100; i++) {
  
var randomX = Math.round(-200 + Math.random() * 700);
var randomY = Math.round(-200 + Math.random() * 700);
var speed = .2+Math.random()*3;
var size = 5+Math.random()*100;
  
var circle = new Circle(mainContext, 100, speed, size, randomX, randomY, 0);
circles.push(circle);
  
}

Because we are going to be creating many circles, all of the code needed for creating the circle is placed inside our for loop which runs from 0 to 99:

for (var i = 0; i < 100; i++) {

In other words, any code inside this loop will run 100 times...which means 100 circles will be created as well. You can adjust this number to alter the number of circles that you will see. Just note that the higher the number, the more circles that will be animated, the slower your performance!

Next up are the various variables that define the characteristics of the circle you will be creating:

var randomX = Math.round(-200 + Math.random() * 700);
var randomY = Math.round(-200 + Math.random() * 700);
var speed = .2+Math.random()*3;
var size = 5+Math.random()*100;

The randomX and randomY variables help determine where on your canvas the circle's initial position will be. Our canvas's size is a square of 500 pixels by 500 pixels, and we want to give 200 pixels extra all around to give our circles some ability to live outside the box - literally!

Here is a basic diagram of what we are trying to do:

canvas boundarie

The -200 (0 - 200) and 700 (500 + 200) correspond to the position of the green area relative to our canvas's top-left corner which is 0, 0.

The next two lines define how fast our circle will move and its size:

var speed = .2+Math.random()*3;
var size = 5+Math.random()*100;

Once you have all of these values, all that is really left is to create our circle object. This is handled by the following two lines:

var circle = new Circle(mainContext, 100, speed, size, randomX, randomY);
circles.push(circle);

In the first line, we create our Circle object by calling the constructor and passing in the following data:

  1. The context variable that defines our 2D Canvas API
  2. The radius of the path our circles will be moving in
  3. The speed
  4. The size of the circle
  5. The X position between -200 and 700
  6. The Y position between -200 and 700

Except for the radius value that I hard-coded to 100, all of the other variables you saw declared and described earlier have a certain level of randomness in them.

Once our circle object is created, we add them to the circles array that we created earlier:

circles.push(circle);

At the end of the loop, our circles array will contain a reference to every circle object we have created. This is important because this array is the only link between the circles and what you see drawn on the screen.

The final thing we do is start the timer that will call a function that is responsible for moving our circles around:

setInterval(draw, 20);

The way we do that is by using setInterval. The setInterval method allows you to automatically keep calling another function, and you get to specify the interval between function calls.

In our case, we are calling the draw function, and we are going to be calling it once every 20 millseconds. Let's look at the draw function next.

Drawing the Circles
The draw function is called every 20 milliseconds, and it is responsible for moving your circles around by having them be redrawn at slightly different locations. You redraw something fast enough with just enough variation between redraws, you have animation!

Here is what our draw function looks like:

function draw() {
mainContext.clearRect(0, 0, 500, 500);
 
for (var i = 0; i < circles.length; i++) {
 
var myCircle = circles[i];
myCircle.update();
}
 
}

The first thing I do is clear our canvas so that new things can be drawn:

mainContext.clearRect(0, 0, 500, 500);

The reason is that there is no concept of moving something around the canvas like you may see when moving HTML elements around. You have to explicitly clear all of your contents before drawing new things. Otherwise, you'll just be drawing on top of existing content...and that will look pretty bad similar to smudging paint.

After you clear your canvas, everything is set to draw your circles in a slightly new location:

for (var i = 0; i < circles.length; i++) {
  
var myCircle = circles[i];
myCircle.update();
  
}

Notice what the range of our loop is. It starts at 0 and ends at the length of our circles array. The reason is that we will be accessing each circle object in the circles array and calling the update method on it:

var myCircle = circles[i];
myCircle.update();

By now, you've seen me mentioning the circle object a few times. The circle object is, as you can guess, based on the Circle class. Let's look at the Circle class in greater detail in the following sections!

Defining our Circle Class
Each circle you see is based on a Circle class that stores your individual circle's speed, direction of rotation, size, and more. Besides defining the look of your circle, your class also defines the drawing and updating behavior.

Let's first look at our Circle function which will be used as a constructor for our circle object:

function Circle(context, radius, speed, width, xPos, yPos) {
  
this.context = context;
this.radius = radius;
this.speed = speed;
this.width = width;
this.xPos = xPos;
this.yPos = yPos;
 
this.counter = 0;
 
this.sign = Math.floor(Math.random()*2);
 
if (this.sign == 1)
{
this.sign = -1;
}
else
{
this.sign = 1;
}
}

The Circle function is pretty straightforward. The first part involves taking the arguments passed into it and storing them as variables on the Object created when we instantiate it with the new keyword:

function Circle(context, radius, speed, width, xPos, yPos) {
 
this.context = context;
this.radius = radius;
this.speed = speed;
this.width = width;
this.xPos = xPos;
this.yPos = yPos;
 
this.counter = 0;
 
this.sign = Math.floor(Math.random()*2);
 
if (this.sign == 1)
{
this.sign = -1;
}
else
{
this.sign = 1;
}
}

You saw these values getting populated and passed into our constructor when we were creating our Circle objects for the first time and adding them to our circles array:

var circle = new Circle(mainContext, 100, speed, size, randomX, randomY);
circles.push(circle);

Getting back to the code at hand, in the next line, I declare a new variable called counter and initialize it to 0:

this.counter = 0;

Notice that this variable, too, is declared on our Object by using the this keyword. The counter variable is quite important as it incrementing is responsible for our circles actually changing their position. I will explain a bit more about this shortly.

The last thing we do in our Circles function is determine the direction of movement by creating a variable called sign that stores either a 1 or a -1:

var signHelper = Math.floor(Math.random()*2);
 
if (signHelper == 1)
{
this.sign = -1;
}
else
{
this.sign = 1;
}

There is a signHelper variable that helps us figure out the value of sign by storing either a 0 or 1 depending on what the random number function returns. If signHelper's value is 1, we set our sign variable's value to -1. If signHelper is 0, we set our sign variable to be 1.

The 1 and -1 are used as multipliers to determine whether we increment up or down. You'll see shortly when we look at our counter variable again. Now, let's move away from declaring variables to actually looking at how our circles get drawn.

Add Function to Draw and Update the Circles
Everything related to drawing and updating the circles is handled by the update function:

Circle.prototype.update = function() {
this.counter += this.sign*this.speed;
this.context.beginPath();
this.context.arc(this.xPos + Math.cos(this.counter / 100) * this.radius, this.yPos + Math.sin(this.counter / 100) * this.radius, this.width, 0, Math.PI * 2, false);
this.context.closePath();
this.context.fillStyle = 'rgba(185, 211, 238, 0.5)';
this.context.fill();
};

The first thing to note is that the update function is designed to live as a function on all Circle objects:

Circle.prototype.update = function() {

Discussing prototypes and why this function isn't just nested inside our constructor goes beyond the bounds of this tutorial, but just know that every Circle object you create has the ability to call its update method. If you recall, this method gets called every 20 milliseconds as part of our call to the draw function you saw earlier.

Critical to making your circle move is the counter variable that you saw earlier. Now, the counter variable is actually being used:

this.counter += this.sign*this.speed;

Notice that the value of the counter variable is being incremented by the product of the speed and the sign. You can see the role the sign variable plays more clearly here. If the value of sign is negative, your counter variable is decreasing. If it is positive, your counter variable increases!

The next three lines help draw your circle:

this.context.beginPath();
this.context.arc(this.xPos + Math.cos(this.counter / 100) * this.radius, this.yPos + Math.sin(this.counter / 100) * this.radius, this.width, 0, Math.PI * 2, false);
this.context.closePath();

The first and third line tell your context object to start drawing a path and to close the path you've drawn respectively. The real magic happens in the second line where I call the arc method to define the boundaries of our circle.

The first two arguments specify the x and y position of the circle we are wishing to draw.

Notice that I am offsetting both of these values by the trigonometric functions Math.cos and Math.sin with the counter variable being used to define the angle. As you may recall from math class, both the Cosine and Sine functions are periodic.

As the counter value keeps increasing, the value for Math.cos and Math.sin oscillates between 0 and 1. This range of 0 and 1 is multiplied by the radius to give you the wide variation you see when the circle is drawn. You can learn more about how these trigonometric functions work by checking out an earlier Trigonometric Animations article.

The third and fourth arguments define the starting angle and the end angle of your circle. Because we want a full circle, the starting angle is 0 and the closing angle is 2 PI.

The last argument specifies whether you want the circle to be drawn clockwise or counterclockwise. A value of true means the circle is drawn clockwise. Because we are drawing a complete circle, it doesn't really matter what we are trying to do.

The last thing we are going to do is specify the color and opacity of the circle. This is accomplished by the following two lines:

this.context.fillStyle = 'rgba(185, 211, 238, 0.5)';
this.context.fill();

The fillStyle property on our context object specifies the RGBa (red, green, blue, alpha) values that make our circle look the way it does. You can learn more about RGBa values in the Reading RGB / aRGB Values tutorial.

Once you have specified your colors, you actually apply it by calling the fill method on your context object.

Conclusion
And with that, you are done learning all about how you can use the canvas object's 2D API combined with JavaScript classes to have a hundred circles randomly moving around in a circular path. Wasn't that fun?

Need Help?

If you have questions, need some assistance on this topic, or just want to chat - please drop by our friendly forums and post your question. There are a lot of knowledgeable and witty people who would be happy to help you out. Plus, we have a large collection of smileys you can use

Share

Did you enjoy reading this and found it useful? If so, please share it with your friends:

If you didn't like it, I always like to hear how I can do better next time. Please feel free to contact me directly at kirupa[at]kirupa.com.

Cheers!

Kirupa Chinnathambi
about | facebook | twitter


cloud storage
                          EdgeCast CDN
kirupa.com's fast and reliable hosting provided by Media Temple. Creative web apps. Make your own free flash banners and photo slideshows.
HTML5 CSS3 Mobile Gallery for iPhone, iPad Flash effects. Art without coding.
Flipping Book - page flip flash component. Flash-Gallery.com - Get your flash photo gallery (flash component or swf gallery
Learn how to advertise on kirupa.com