It's extremely common to need to position display objects in a grid formation. I usually find myself doing it when displaying thumbnails for an image gallery. This post will take you through the process step by step.
For..Loops
The first thing we need to know when creating grids is how to use for..loops to create a simple row of clips. This is covered in Chapter 4 of the book. In order to follow along with the code examples, you'll need to create a square movie clip and then export it for ActionScript in your Library, using "Box" as its linkage class:

Be sure to use the class name Box
Once you've done that, try running this code within your timeline:
-
for (var i:int = 0; i <4; i++) {
-
// Box exported for actionscript in your library
-
var box:Box = new Box();
-
box.x = 50 + i * box.width;
-
box.y = 50;
-
addChild(box);
-
}
The above code will create four boxes lined up in a row from left to right. We achieve this by offsetting each box's x position by i * box.width (as seen on line 4). Assuming the Box clip is 50-pixels wide, this little bit of code causes the first box to be positioned at 50 on the x axis (50 + (0 * 50)), the second box to be positioned at 100 on the x axis (50 + (1 * 50)), etc... The result looks like this:

Controlling spacing between the boxes can be achieved by adding to the box's width property and then multiplying by i. Like this:
-
for (var i:int = 0; i <4; i++) {
-
var box:Box = new Box();
-
// add 10-pixel space between boxes
-
box.x = 50 + i * (box.width + 10);
-
box.y = 50;
-
addChild(box);
-
}
In order to arrange clips iin a grid formation we basically just want to draw more than one row of clips. Like this:
-
var i:int;
-
var box:Box;
-
-
for (i = 0; i <4; i++) {
-
box = new Box();
-
box.x = 50 + box.width * i;
-
// box y stays the same
-
box.y = 50;
-
addChild(box);
-
}
-
-
for (i = 0; i <4; i++) {
-
box = new Box();
-
box.x = 50 + box.width * i;
-
// box y is offset by box.height
-
box.y = 50 + box.height;
-
addChild(box);
-
}
-
-
for (i = 0; i <4; i++) {
-
box = new Box();
-
box.x = 50 + box.width * i;
-
// box y is offset by box.height * 2
-
box.y = 50 + box.height * 2;
-
addChild(box);
-
}
-
-
for (i = 0; i <4; i++) {
-
box = new Box();
-
box.x = 50 + box.width * i;
-
// box y is offset by box.height * 3
-
box.y = 50 + box.height * 3;
-
addChild(box);
-
}
If you have some experience programming, you may already see how this can be optimized, but it may not be obvious to everyone. We're basically doing the same thing to the y property that we did to the x. We're offsetting it by box.height * 2 or box.height * 3, etc... So to optimize the above code we could write:
-
var rows:int = 4;
-
var cols:int = 4;
-
for (var py:int = 0; py <rows; py++) {
-
for (var px:int = 0; px <cols; px++) {
-
var box:Box = new Box();
-
box.x = 50 + box.width * px;
-
box.y = 50 + box.height * py;
-
addChild(box);
-
}
-
}
Try tracing out px and py to get a better understanding of how this code works.
Seeing a nested for..loop can be confusing. But if you go back and forth between this code and the previous lengthy example it should start to become clear. Note the use of the variable names px and py in our for..loops, short for position x and position y.
In cases where the number of elements in the grid is dynamic, you might not know how many boxes you're going to need to draw. For example, if you have an image gallery that pulls thumbnails from an XML file, you might need to load three images or you might need to load ten. Some of the choices you make when writing dynamic code will rely partially on design, but the basic concepts will stay the same. Lets look at some code that will draw a grid of any number of boxes:
-
var boxNum:int = 15;
-
-
// we need to know how many columns our
-
// grid is going to have
-
var cols:int = 4;
-
-
// calculate how many rows we need based on
-
// boxNum and cols
-
var rows:int = Math.ceil(boxNum / cols);
-
-
// the number of boxes attached to the stage
-
var boxCount:int = 0;
-
-
for (var py:int = 0; py<rows; py++) {
-
for (var px:int = 0; px<cols; px++) {
-
-
// make sure we haven't exceeded boxNum
-
if (boxCount <boxNum) {
-
var box:Box = new Box();
-
box.x = 50 + box.width * px;
-
box.y = 50 + box.height * py;
-
addChild(box);
-
boxCount++;
-
}
-
-
}
-
}
The variable boxNum is all that needs to change if we want to change the number of boxes in the grid. This example makes a design choice by setting the cols variable to 4. We then calculate how many rows we need to accommodate boxNum. This means that the grid will expand vertically as boxNum is set to higher values.
Our rows value is calculated by dividing boxNum by cols and rounding the result up with Math.ceil. If boxNum is 4, 4 divided by 4 is 1 - so, we know we only need one row. If boxNum is 5, 5 divided by 4 is 1.25 - round that up to 2 and we've figured out that we need two rows to accommodate five boxes.
To prevent drawing too many boxes, we have a counter called boxCount. On line 18, before we add a box to the stage we check to make sure that boxCount hasn't exceeded boxNum and if it hasn't we add our box to the stage and increment boxCount.
This code will work in your timeline as long as you still have a movie clip in your library that is export to ActionScript as Box.
Using Modulus
There is another way to create a grid using only one for..loop. It's a little less intuitive than the nested for..loop because it requires knowledge of the modulo operator %.
Back in grade school there was a time when none of us knew how to use fractions or decimal values. So if a teacher said, "What's 10 divided by 3?" We might say, "3 remainder 1. Because 3 goes into 10 3 times with a remainder of 1." All modulus does is give us the remainder from a division equation. So 10 % 3 equals 1. That is, when using modulus, we only care about the remainder, not how many times the a number goes into another. If you don't quite get it yet, try this code in your timeline for a few seconds and then read through the results that get traced to your output window:
-
addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
-
-
var counter:int = 0;
-
function onLoop(evt:Event):void {
-
counter++;
-
var phrase:String = "";
-
var answer:int = int(counter / 10);
-
var remainder:int = counter % 10;
-
phrase = counter + " / 10 = " + answer + " remainder " + remainder;
-
trace(phrase);
-
}
As a result of running this code your output window will look something like this:
1 / 10 = 0 remainder 1 2 / 10 = 0 remainder 2 3 / 10 = 0 remainder 3 4 / 10 = 0 remainder 4 5 / 10 = 0 remainder 5 6 / 10 = 0 remainder 6 7 / 10 = 0 remainder 7 8 / 10 = 0 remainder 8 9 / 10 = 0 remainder 9 10 / 10 = 1 remainder 0 11 / 10 = 1 remainder 1 12 / 10 = 1 remainder 2 13 / 10 = 1 remainder 3 14 / 10 = 1 remainder 4 15 / 10 = 1 remainder 5 16 / 10 = 1 remainder 6 17 / 10 = 1 remainder 7 18 / 10 = 1 remainder 8 19 / 10 = 1 remainder 9 20 / 10 = 2 remainder 0 21 / 10 = 2 remainder 1 22 / 10 = 2 remainder 2 23 / 10 = 2 remainder 3 24 / 10 = 2 remainder 4 25 / 10 = 2 remainder 5 26 / 10 = 2 remainder 6 27 / 10 = 2 remainder 7 28 / 10 = 2 remainder 8 29 / 10 = 2 remainder 9 30 / 10 = 3 remainder 0
One of the useful parts of this is the fact that we get a counter that goes from 0-9 and then back down to 0 again. Look at the remainder value above to see this.
If we go back to our consolidated grid example and add some trace statements you might begin to see how you could use the modulo operator to achieve the same thing, but with only one for..loop:
-
var rows:int = 4;
-
var cols:int = 4;
-
for (var py:int = 0; py<rows; py++) {
-
for (var px:int = 0; px<cols; px++) {
-
var box:Box = new Box();
-
box.x = 50 + box.width * px;
-
box.y = 50 + box.height * py;
-
trace(px, py);
-
addChild(box);
-
}
-
}
Our output window now reads:
0 0 1 0 2 0 3 0 0 1 1 1 2 1 3 1 0 2 1 2 2 2 3 2 0 3 1 3 2 3 3 3
If you'd like a challenge, stop reading this post for a few minutes and see if you can convert the nested for..loop to one for..loop that uses the modulo operator. To see how we do this, just scroll down:
Modulo Grid
-
var boxNum:int = 15;
-
var cols:int = 4;
-
-
for (var i:int = 0; i<boxNum; i++) {
-
var box:Box = new Box();
-
box.x = 50 + box.width * (i % cols);
-
box.y = 50 + box.height * int(i / cols);
-
addChild(box);
-
}
You might look at this and think it makes our dynamic grid look overly complex. Consider this example:
Roll your mouse over the grid
Download Interactive Grid (7.2 KB, 233 hits)
In this example, we add inactive boxes to complete our grid. This is a common design technique and is simpler to do with the nested for..loop method. Take a look at the code:
-
//set how many boxes we want
-
var boxNum:int = 17;
-
// set how many columns our grid is going to have
-
var cols:int = 4;
-
// calculate how many rows we need, based on boxNum
-
var rows:int = Math.ceil(boxNum / cols);
-
// the number of boxes attached to the stage
-
var boxCount:int = 0;
-
for (var py:int = 0; py <rows; py++) {
-
for (var px:int = 0; px <cols; px++) {
-
-
var box:Box = new Box();
-
box.x = 50 + box.width * px;
-
box.y = 50 + box.height * py;
-
addChild(box);
-
-
// only add listeners if box should be active
-
if (boxCount <boxNum) {
-
box.buttonMode = true;
-
box.addEventListener(MouseEvent.ROLL_OVER, onOver,
-
false, 0, true);
-
box.addEventListener(MouseEvent.ROLL_OUT, onOut,
-
false, 0, true);
-
} else {
-
// box is inactive
-
box.alpha = 0.5;
-
}
-
-
boxCount++;
-
}
-
}
-
-
function onOver(evt:Event):void {
-
var box:MovieClip = MovieClip(evt.target);
-
addChild(box)
-
box.scaleX = box.scaleY = 1.3;
-
}
-
-
function onOut(evt:Event):void {
-
evt.target.scaleX = evt.target.scaleY = 1;
-
}
We've added an else to our if statement and moved the code that creates and adds the box outside of the conditional. Now the conditional only adds listeners if the box is supposed to be active, otherwise it sets the alpha property of the box to 50-percent.
This post has presented a few ways of writing code to arrange symbols into a grid. Choosing which method to use will depend largely on the task you are trying to accomplish, and your comfort level with the covered ActionScript examples. Getting your grid to appear as expected is the first thing you should strive for. Once you've succeeded, you can then consider alternate approaches, balancing code optimization with code clarity along the way.
Print This Post


(13 votes, average: 4.31 out of 5) 






























Question: If I were to have an xml file of videos and or images how is it written for the boxNum = item.video(xml)
? In other words, the thumbnail of the image or image of the video would load into the boxes from the xml data.
Thanks!
Ken,
This is a good question. I'm planning on doing a post that shows how to take the concepts I talked about here and tie them together with an xml file and some external images. The post should be up in the next day or two.
Hi,
Thank you very much for your brilliant tutorial. I tried to follow your logic when uploading images to a grid gallery, however I am stuck! instead of looping through all the images it gives me the following error: Error #2044: Unhandled IOErrorEvent:. text=Error #2035: URL Not Found. Can you see where am I going wrong and how can I silve the problem? Thank you
This is my code:
[snipped]
image_loader.load(new URLRequest("flash_images/" images[imageCount]));
[/snipped]
@nevena, we're not always able to debug code sent in, but I think I may have noticed your problem. The code you sent was snipped because it was fairly lengthy, but also because we're not yet happy with how code is formatted in the comments. However, I've left the relevant line visible, above.
The error you saw is one of the errors that is quite clear, in that it really does mean the URL you sent via URLRequest couldn't be found. This can occur for a number of reasons, including no asset at that location, a badly formed URL, the server is busy or down, etc.
In this case, you're missing a concatenation operator (+) in your URLRequest resulting in a badly formed URL. Just change the line shown in your comment to:
image_loader.load(new URLRequest("flash_images/" + images[imageCount]));
That assumes your images are in a directory called "flash_images" which, itself, is in the same location as your SWF.
Great blog and book. I recommend it to anyone thats starting with as3
Curious if you had time to post a show that explained what Ken Griffin (#1) asked for...I'm curious too...loading XML images into this thumbnailed grid. Thanks for the help.
Hi
thanks for this great tutorial (and not just that one).
i`m building a gallery with this notion of the grid and i try to use Tweener to make the thumb appear one by one and not all at the same time, but i fail to do so.
how can i do that?
best regards
As each box gets created (var box:Box = new Box();) in the For ...loop, wouldn't it be best to uniquely name each box? like: box1, box2, box3? .. in case they needed to be referred to from somewhere else (not just mouse over events)?
If so, how would you do this? --- var box:Box = new Box();--- doesn't seem to offer that option.
Yes i would like to know the same thing how can we dynamically add these clips with a unique name like AS2 attachMovie("myCLip" i);. Then add the unique id to array and access them?
or say you use dynamic text in the clip or a image how would you point xml into it?
also i can barely see what I'm typing in this form haha. Good tutorial though.
@Riley, @thawootah:
Naming the box is accomplished by setting the name property, at line 13 of the last script above. (The new line is in bold.)
var box:Box = new Box();
box.name = "box" + boxCount;
However, you can't access an instance name that is programmatically assigned, the same way as manual assignments. Instead, use the following:
var clip:MovieClip = getChildByName("box0");
or, if in a loop:
var clip:MovieClip = getChildByName("box" + i);
where i is the loop counter.
@thawootah, would you mind sending an email using the contact form to describe the trouble you're having with the comment form? It looks very clear to me so I think this might be a platform/browser-specific CSS issue. Can you tell me which OS/browser you're using and why you can't see what you're typing? Thanks.
@ron:
Zevan wrote an introduction to TweenLite that does what you're asking about. The last example in the post, the fake banner, tweens in 5 elements or so, sequentially. You should be able to adapt the technique for Tweener fairly easily. Let us know if that helps.
@Ken, pa_dutch, thawootah:
There are a bunch of ways to do this, but I've written one that requires the least change to the existing script so it can be easily added here and easily explained. Based on the questions, we're assuming you know how to get the path of the object for each box (thumbnail or other content) from your XML. We may follow this up with a more in-depth post, but this will definitely get you started. Just add the following to the if segment of the if..else statement (after line 23) of the last script:
var ldr:Loader = new Loader(); box.addChild(ldr); var loadPath:String = "path_to_content_from_xml.jpg"; ldr.load(new URLRequest(loadPath)); ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onContentReady, false, 0, true); function onContentReady(evt:Event):void { var loaded:DisplayObject = evt.target.content; loaded.x -= box.width/2; loaded.y -= box.height/2; loaded.width = box.width; loaded.height = box.height; }To load the external display data, you need an instance of the Loader class. The first two lines of the insert create the instance and load it to the box.
The next block of the insert loads the content and process it after loading for cosmetic purposes. The first line is the thumbnail path that, based on the questions, would be parsed from an XML instance. The next line loads the content, and the third line of the block adds an event listener that will trigger a function that positions and scales the content after loading.
Finally, the event handler function is added. This will vary depending on your needs. In the original example, a movie clip in the library contained a shape, an the clip has a center registration point. All this function does is set the x and y of the loaded content to the upper-left corner of the container, and sets the width and height of the loaded content to that of the container.
This entire listener and function could be eliminated if you sized the loaded content to the container and used a container with a top-left registration point. Or, you may require additional treatment, such as when the content doesn't match the shape of the container. That is, this simple example just matches the width and height of the container, but doesn't scale proportionally. You could add code to accomplish that if desired, or even switch to using a component that will auto-scale, if you prefer.
Thanks for the help! Yeah AS3 should be easy after i figure out all the new lingo. Sometimes i feel like i just switched from swish to flash 5.
about the form, I'm using XP and Firefox 3. the text is a few pixels too small but It's no biggie i just zoom in. here's a screen shot.
image shack
@thawootah, Yikes! Thanks for that screenshot. We still have a lot of work to do on getting the site to where we want it, and I'll add this to the list. I appreciate the comment.
I'm dynamically loading the movieclips on the stage to be used as buttons.
I need 6 to be displayed at once on a single page.
When loading more than six , I would like this to create a new page that contains the rest, and so on in sets of six.
In addition I would like a new thumbnail button be appear (page 1, page 2, ecc..) in order to jump between the various pages.
Can anyone help ?
Thanks