Download the code for Hundreds of Shapes
By now you have a good sense of how to draw shapes and paths in Canvas. In our last exercise we drew nine shapes—three squares, three circles, and three triangles. What if we wanted to draw a hundred shapes? To calculate the placement of each shape and type it into our code would take lots of time.
Fortunately there's a way to speed up the process. We can use a for loop. A for loop lets you repeat a line (or several lines) of code. If we combine variables with some simple calculations, we can quickly cover our screen with shapes. Let's try it out.
To keep our files organized, it's a good idea to create a folder for each exercise we create. We can then place all the files we create for an exercise inside that exercise folder. Make sure that this new folder is placed inside your main class folder and call it, hundreds-of-shapes. Next, inside the hundreds-of-shapes folder, create another folder for the JavaScript file you're about to create. Name this subfolder, js.
With our folders set up, we can create a simple HMTL page and save it in our hundreds-of-shapes folder. (Just to keep things consistent, we'll call this new HTML file hundreds-of-shapes.html. Now we have a folder, an HTML file, and soon a Javascript file each with the same name, hundred-of-shapes. We can do this because they are different file types: one is a folder, another an HTML file, and another a JavaScript file.)
Next, change the title tag of the HTML file to Hundreds of Shapes, add a canvas tag along with a script tag with its src set to js/hundreds-of-shapes.js.
The full HTML page looks like this:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hundreds of Shapes</title>
</head>
<body>
<canvas id="myCanvas" width="1200" height="800">
You'll need a contemporary browser with javascript
turned on to be able to see this page
</canvas>
<script src="js/hundreds-of-shapes.js"></script>
</body>
</html>
Now we'll create a Javascript file in Dreamweaver and save it as hundreds-of-shapes.js inside our subfolder named js. In Dreamweaver, select File>New, choose JavaScript from the New Document panel's Document Type column, and press Create at the bottom right of the panel.
In this section we'll draw a line of 100 squares across our screen. We need to set up a few variables to do that. We're going to use a slightly different syntax that works well when you have a lot of variables. To initialize several variables, type the var keyword just once and then list each variable and its value, separated by a comma. When you reach the last variable, end it with a semicolon to let the browser know you're finished defining and initializing your variables.
Here's how we'll set up our variables to draw 100 squares:
//Hundreds of Shapes
//Draw hundreds of squares and circles
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
squareXAmt = 12,
squareY = 40,
squareW = 10,
squareH = 10,
totalSquares= 100;
The first two lines should look familiar (though the syntax is slightly different)—we access our canvas and set up the context.
The next five variables are for the squares we plan to draw. We're setting the first square's Y position (squareY) and the width and height of all squares (squareW and squareH). We'll use squareXAmt to place the square's X position–we've called it squareXAmt (square X amount) instead of simply squareX because we'll use it as part of a calculation to determine the X position. You'll see how we do that in a minute. The last variable—totalSquares—determines how many squares we'll draw on the screen.
Next, we'll give the squares a color by setting the context's fillStyle property. We'll use the same blue that we used in the last exercise, Draw Shapes. This time, though, instead of using a hexadecimal number, we'll use decimal numbers for the red, green, and blue color channels. When we use decimal numbers for color, each color ranges in value from zero to 255, with zero being completely black and 255 being a bright, intense red, green, or blue. Let's use the same blue we used in the last exercise but convert it from a hexadecimal number to an RGB decimal.
context.fillStyle = 'rgb(21, 138, 212)';
Don't forget the quotes that surround the rgb() color values—fillStyle takes a string of characters and strings are always surrounded by quotes (the numbers, whether decimal or hexadecimal, are part of that string). If you want to use a different color, use Photoshop's color picker to find your RGB values.
Now let's create a for loop. It looks like this:
for (var i=0; i<totalSquares; i++) {
context.rect(squareXAmt * i, squareY, squareW, squareH);
}
The syntax looks complex at first, but it'll soon seem familiar and not that difficult.
We start with the for keyword and then three statements inside parentheses which are followed by a left-facing curly bracket. We close the for loop with a right facing curly bracket. The code that we want to repeat falls inside those curly brackets. Right now we just have one line of code, but you can have as many lines of code as you want. This line of code is repeated a hundred times, each time it calls context's rect() method to draw a square, resulting in a hundred squares.
The basic format for a for loop is this:
for (statement 1; statement 2; statement 3) {
line(s) of code to run
}
Let's go back to the three statements inside the parentheses. This is where the logic of the for loop happens—it determines how many times to repeat the code inside the curly brackets.
Our first segment is:
var i=0;
Notice that each segment is separated by a semicolon, just like the lines of code in JavaScript. In this first statement, we're initializing a counter called i to zero. The counter i is just a variable like any other; the convention is to use just one letter as the for loop's counter but you could call it something more descriptive if you like.
Our second statement is:
i<totalSquares;
This statement is a conditional statement. If the condition is true, we keep looping through the code between the curly brackets; if the condition is false, we stop looping. You can read i<totalSquares; as “Keep on looping while counter i is less than 100 (the value of the variable totalSquares).”
Our third and last statement is:
i++
i++ is a shorthand way to say, increment the counter i by 1 at the end of each loop. Since our counter starts at zero and is incremented by one after each loop, our middle conditional statement—i<totalSquares;—will be true 100 times. On the 101th time, it'll be false and the for loop stops looping.
A little warning about for loops: if you're not careful, you can set up a conditional statement that never becomes false and the for loop just keeps on looping indefinitely. Computer programmers call this an “infinite loop.”
If, for example, I didn't increment the i counter by 1 in the third statement, i would stay at zero and always be less than 100, and our loop would never stop.
If you have a for loop in your code and you notice that your browser becomes sluggish and your script seems stuck, it could be that you're caught in a loop. Stop your script by closing the tab you're viewing in your browser, return to your code, and see if you've created a condition that can never be false.
You can also place console.log() statements inside your loop to show the values of the variables you're checking in the for-loop's conditional statement. (Remember the console.log() code we used in our first program, Hello World?). After you've added your console.log() code, go to a browser, open the HTML page that houses your script, open the browser's Console panel, and watch the variable values print out with each loop. You'll soon see why your conditional statement is never false.
Here's the for loop again with all three statements in place:
for (var i=0; i<totalSquares; i++) {
context.rect(squareXAmt * i, squareY, squareW, squareH);
}
This for loop initializes a counter to zero, checks if that counter if less than one hundred—if it is, it keeps on looping—and increments the counter by one at the end of the loop.
Let's look now at the line of code inside the loop:
context.rect(squareXAmt * i, squareY, squareW, squareH);
Here we have context's rect() method again. Remember that rect() takes four arguments: x and y position followed by width and height. In the past, we typed in numbers for those arguments. In our last exercise, our rect() methods looked like this:
context.rect(440, 40, 100, 100);
context.rect(560, 40, 100, 100);
context.rect(680, 40, 100, 100);
What if we wanted to draw a hundred square shapes? We could type in rect() a hundred times, but there's an easier way to draw our shapes. If we replace rect()'s number arguments with variables and add a for loop, we can draw a hundred shapes with just a few lines of code. With each loop, we update the first argument—the x position of the square—and draw each subsequent square to the right of its neighbor.
Let's look at our line of code again:
context.rect(squareXAmt * i, squareY, squareW, squareH);
The last three arguments are the variables squareY, squareW, and squareH. If we look back to the beginning of our code, these variables have values of 40, 10, and 10. So our squares Y position is 40 pixels from the top of the screen and each square has a width and height of 10.
Now look at the first argument. We have two variables—squareXAmt and the counter i—and we're multiplying them together. squareXAmt has a value of 12 and at the beginning of the loop and i is zero. At the end of each loop, i increases by one. That means the first time through the loop we're multiplying 12 by 0 and our first square's X position is set to zero. Our condition of i<100 is still true, so we loop through the code again. This time, i is equal to one, so our calculation for the X position becomes 12 x 1 = 12. Our counter i continues to increase by one until it's equal to 100; each increase in i draws the current square 12 pixels to the right of its neighbor:
12 x 0 = 0
12 x 1 = 12
12 x 2 = 24
12 x 3 = 36
12 x 4 = 48
.
.
.
12 X 97 = 1164
12 x 98 = 1176
12 x 99 = 1188
When i = 100, i<100 is no longer true, and the loop ends. We've drawn a row of squares, positioned dynamically through a simple calculation. for loops combined with variables help you quickly perform repetitive tasks.
To see the squares, add a fill() method after the for loop:
context.fill();
The code so far looks like this:
//Hundreds of Shapes
//Draw hundreds of squares and circles
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
squareXAmt = 12,
squareY = 40,
squareW = 10,
squareH = 10,
totalSquares= 100;
context.fillStyle = 'rgb(21, 138, 212)';
for (var i=0; i<totalSquares; i++) {
context.rect(squareXAmt * i, squareY, squareW, squareH);
}
context.fill();
Save your Javascript file, start up a browser, and open your HTML page. You'll see 100 blue squares in a row.
Now let's create a hundred circles. At the top of your script, just below the square variables, add some variables for the circles we plan to draw. Generally you place the variables you plan to use throughout your script at the top of your script—we want to give those variables initial values before we use them in the code's main logic.
//Hundreds of Shapes //Draw hundreds of squares and circles var canvas = document.getElementById('myCanvas'), context = canvas.getContext('2d'), squareXAmt = 12, squareY = 40, squareW = 10, squareH = 10, totalSquares = 100,
centerX = 60, centerY = 100, radius = 30, startRad = 0, endRad = 2 * Math.PI, totalCircles = 100;
Notice we've replaced the semi-colon at the end of totalSquares' value with a comma, added some new variables, and finished our list of variables with a semi-colon at the the end of totalCircles' value.
If you remember the arc() method we used to draw circles in the last exercise, these variables should look familiar. arc() takes five arguments: the arc's X and Y center point, the arc's radius, and its starting and ending radians. We also have a totalCircles variable set to 100; we'll use that to set a limit on the number of circles we draw.
Here's our first attempt to draw a 100 circles using a for loop. Add this code at the end of your script:
for (var j=0; j<totalCircles; j++) {
context.beginPath();
context.arc(centerX, centerY, radius, startRad, endRad);
context.fill();
centerX = centerX + radius * 2;
}
Save your JavasScript file, switch to your browser, and refresh your HTML page(press Cmd + R on a mac or Ctrl + R on a PC to refresh your browser). What do you see? Do you see a hundred circles? If not, why not?
We have a single row of circles but they don't add up to a hundred circles. Let's look at our for loop to see what's going on.
Our three statements in the for loop—var j=0; j<totalCircles; j++—are very close to the statements in the last for loop we created. This time our counter is called j; it's set to zero in the first statement. Our conditional statement—the statement that's checked at the end of each loop and if it's true, we loop again—is also similar to our previous for loop. It says, “keep looping while j is less than totalCircles or 100.” In the third statement, we increment j by one—this happens at the end of each loop. Nothing really new there.
Inside our for loop we have four lines of code. The first three should look familiar. We use the beginPath() method to start a new path, the arc() method to draw a circle, and the fill() method to fill the circle with a color. In the previous exercise, we typed in numbers for the arc() method's arguments, now we're using variables.
The last line of code inside the for loop performs a calculation on the circX variable—this is the variable we use to position the circle in the arc() method.
circX = circX + radius * 2;
This code sets circX to its current value plus the radius' value times two—multiply a circle's radius by two and you have the circle's diameter or width. Since this line of code runs with each loop, we're adding the diameter of a circle to the circX variable with each loop. This means each subsequent circle is placed one circle's diameter to the right and we end up with a row of circles.
The size of our circles though (determined by the radius variable) is too large to fit all one hundred circles on our screen. Consequently our circles run off the screen once we've drawn just twenty of them.
What we need to do when we're at the end of the screen is move down a row and draw another batch of circles. When we reach the end of the screen again, we move down to the next row.
In JavaScript we can use a conditional statement to do this. Conditional statements follow this syntax:
if (condition) {
run some code
}
We need to write a condition in JavaScript that tests if we're at the end of the screen. We can do that by looking at our circX, the x position of the circle's center point, and comparing it against the canvas' width. To do that, lets add one more variable to our group of circle variables just below totalCircles:
//Hundreds of Shapes //Draw hundreds of squares and circles var canvas = document.getElementById('myCanvas'), context = canvas.getContext('2d'), squareXAmt = 12, squareY = 40, squareW = 10, squareH = 10, totalSquares = 100, centerX = 60, centerY = 100, radius = 30, startRad = 0, endRad = 2 * Math.PI, totalCircles = 100,
canvasWidth = canvas.width;
Notice that we replace the semicolon after totalCircles because it's no longer the last variable in our group and we add canvasWidth as a variable that stores the width property of canvas, finishing the line with a semicolon.
In simple language, the condition we need to create to draw several rows of circles is something like this:
if our circle's x point is off screen then
increase the circle's y point to move down a row
and set the circle's x point back to its starting value
In JavaScript, it looks like this:
if (centerX > canvasWidth - radius) {
centerY = centerY + radius *2;
centerX = radius * 2;
}
The first line is our conditional statement. It says, “check if the circle's x center point is greater than the canvas' width minus the radius of the circle.” We subtract a little off the width so that we don't draw circles right to the edge of the canvas—by subtracting a circle's radius from the width, we know we'll have some negative space at the edge of our canvas.
When the condition is met—when we run out of space for circles—we run two lines of code. The first line sets the circX variable back to 60 (two times the radius' value of 30). The second line moves the circY down by the diameter of a circle—if we're on our first row, for example, it increases circY from 100 to 160 pixels.
We place this conditional statement inside our for loop. The complete for loop looks like this:
for (var j=0; j<totalCircles; j++) {
if (centerX > canvasWidth - radius) {
centerY = centerY + radius *2;
centerX = radius * 2;
}
context.beginPath();
context.arc(centerX, centerY, radius, startRad, endRad);
context.fill();
centerX = centerX + radius * 2;
}
Now you can see all one hundred circles.
The last row stops after just five circles. To make it match the other rows, increase the initial value of totalCircles to 114. Now you have a big block of circles.
Save your Javascript file and take a look at your HTML file in a browser. Do you see a block of circles below your row of squares?
Let's make the circles a little more interesting. With our basic logic in place, we can easily draw smaller circles on top of our current circles by changing the fill color and shrinking the radius size of subsequent circles—we can leave the other arguments for arc() just as they are.
Here's another version of our for loop, this time with three different fill colors and circle sizes. The first circle becomes the background circle and each new circle draws on top of the previous circle.
Here's the code:
for (var j=0; j<totalCircles; j++) {
if (centerX > canvasWidth - radius) {
centerY = centerY + radius *2;
centerX = radius * 2;
}
context.fillStyle = 'rgb(118, 202, 19)';
context.beginPath();
context.arc(centerX, centerY, radius, startRad, endRad);
context.fill();
context.fillStyle = 'rgb(21, 138, 212)';
context.beginPath();
context.arc(centerX, centerY, radius/2, startRad, endRad);
context.fill();
context.fillStyle = 'rgb(255, 255, 255)';
context.beginPath();
context.arc(centerX, centerY, radius/4, startRad, endRad);
context.fill();
centerX = centerX + radius * 2;
}
Notice that before we draw each circle we're changing the fillStyle property to change the circle's color. Our first circle is drawn with a radius of 60 pixels, the value we set the radius variable to at the top of our code. The second circle has a radius that's half the size of radius (radius/2), and the third circle has a radius that's a quarter of the first circle (radius/4). Each circle gets smaller and is layered on top of the previous circle.
The complete code looks like this:
//Hundreds of Shapes
//Draw hundreds of squares and circles
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
squareXAmt = 12,
squareY = 40,
squareW = 10,
squareH = 10,
totalSquares = 100,
centerX = 60,
centerY = 100,
radius = 30,
startRad = 0,
endRad = 2 * Math.PI,
totalCircles = 114,
canvasWidth = canvas.width;
context.fillStyle = 'rgb(21, 138, 212)';
for (var i=0; i<totalSquares; i++) {
context.rect(squareXAmt * i, squareY, squareW, squareH);
}
context.fill();
for (var j=0; j<totalCircles; j++) {
if (centerX > canvasWidth - radius) {
centerY = centerY + radius *2;
centerX = radius * 2;
}
context.fillStyle = 'rgb(118, 202, 19)';
context.beginPath();
context.arc(centerX, centerY, radius, startRad, endRad);
context.fill();
context.fillStyle = 'rgb(21, 138, 212)';
context.beginPath();
context.arc(centerX, centerY, radius/2, startRad, endRad);
context.fill();
context.fillStyle = 'rgb(255, 255, 255)';
context.beginPath();
context.arc(centerX, centerY, radius/4, startRad, endRad);
context.fill();
centerX = centerX + radius * 2;
}
To see another way to use for loops to rapidly draw shapes, download the code for Grid of Shapes. In this code we nest a for loop inside another for loop to create a grid of graphics. It's similar in screen output to Hundreds of Shapes but uses different logic.