13
May
08

The Power of Relative Positioning

Understanding the basic display object properties like x, y, width, height etc... is imperative for anyone interested in intermediate level ActionScript. It's important to practice using properties until you understand them well enough to solve problems using them. If you're not comfortable with properties yet, you should read through Chapter 3 and then try the exercises in the Property Practice (simple motion) post.


One of the more common things you'll find yourself doing with properties is using them to position display objects relative to one another.

If you have three arbitrarily placed movie clips on your stage, box0, box1 and box2...

instance names

...you can align them by putting the following code on your main timeline:

// alignment code
box0.x = 100;
box0.y = 100;

// position box1 based on box0's x and width properties
box1.x = box0.x + box0.width;
box1.y = box0.y;

box2.x = box1.x + box1.width;
box2.y = box1.y;

align boxes
Clips aligned with code

The power of this kind of relative alignment may not be immediately apparent from looking at the code. However, try changing the width of one of the boxes, and then test your movie again:

align boxes
Relatively positioned clips remain properly aligned

The clips are still aligned despite having different width values. Continuously aligning clips on an enter frame event opens up some different possibilities:

Roll over each box:

This movie requires Flash Player 9. Please update your player.

Download: align_nav_1.fla  Download align_nav_1.fla (6.9 KB, 155 hits)

Above we've used the align technique to create a row of dynamically resizing buttons. Here's the code:

ActionScript 3.0:
  1. box0.x = 20;
  2. box0.y = 20;
  3.  
  4. setupRollOver(box0);
  5. setupRollOver(box1);
  6. setupRollOver(box2);
  7.  
  8. function setupRollOver(mc:MovieClip):void {
  9.     // add a local variable to each clip for use with Zeno's paradox
  10.     mc.scaleDest = 1;
  11.     mc.buttonMode = true;
  12.     mc.addEventListener(MouseEvent.ROLL_OVER, onOver, false, 0, true);
  13.     mc.addEventListener(MouseEvent.ROLL_OUT, onOut, false, 0, true);
  14.     mc.addEventListener(Event.ENTER_FRAME, onScale, false, 0, true);
  15. }
  16.  
  17. function onScale(evt:Event):void {
  18.     var mc:MovieClip = MovieClip(evt.target);
  19.     mc.scaleX += (mc.scaleDest - mc.scaleX) / 4;
  20. }
  21.  
  22. function onOver(evt:MouseEvent):void {
  23.     evt.target.scaleDest = 3;
  24. }
  25.  
  26. function onOut(evt:MouseEvent):void {
  27.     evt.target.scaleDest = 1;
  28. }
  29.  
  30. addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  31.  
  32. function onLoop(evt:Event):void {
  33.     // align all clips
  34.     box1.x = box0.x + box0.width;
  35.     box1.y = box0.y;
  36.     box2.x = box1.x + box1.width;
  37.     box2.y = box1.y;
  38. }

In the above code the setupRollOver function adds listeners to each box causing their scaleX property to grow to 300% when a roll over event is received. Lines 31-39 run the previously discussed alignment code each time an enter frame event occurs so that the clips remain aligned at all times.

Expanding the Example

If we wanted to add a box to this example, we'd have to duplicate a bit of code and drag out a new box instance from our Library. If we wanted to have ten boxes, our align code would start to look very repetitive:

box1.x = box0.x + box0.width;
box1.y = box0.y;
box2.x = box1.x + box1.width;
box2.y = box1.y;
box3.x = box2.x + box2.width;
box3.y = box2.y;
box4.x = box3.x + box3.width;
box4.y = box3.y;
box5.x = box4.x + box4.width;
box5.y = box4.y;
box6.x = box5.x + box5.width;
box6.y = box5.y;
box7.x = box6.x + box6.width;
box7.y = box6.y;
box8.x = box7.x + box7.width;
box8.y = box7.y;
box9.x = box8.x + box8.width;
box9.y = box8.y;

Let's take a look at how we can get rid of that repetitive code and make the number of boxes in our navigation completely dynamic:

ActionScript 3.0:
  1. // changes the number of boxes in the navigation
  2. var boxNum:int = 8;
  3.  
  4. // for more control we put all our boxes in one movie clip
  5. // we store a reference to that clip in a variable called nav
  6. var nav:MovieClip = new MovieClip();
  7. nav.x = 20;
  8. nav.y = 20;
  9. addChild(nav);
  10.  
  11. // our boxes are pulled out from the Library, positioned,
  12. // added to the display list and passed to setupRollOver
  13. for (var i:int = 0; i <boxNum; i++) {
  14.     var b:MovieClip = new Box();
  15.     b.xb.width * i;
  16.     nav.addChild(b);
  17.     setupRollOver(b);
  18. }
  19.  
  20. function setupRollOver(mc:MovieClip):void {
  21.     mc.scaleDest = 1;
  22.     b.buttonMode = true;
  23.     mc.addEventListener(MouseEvent.ROLL_OVER, onOver, false, 0, true);
  24.     mc.addEventListener(MouseEvent.ROLL_OUT, onOut, false, 0, true);
  25.     mc.addEventListener(Event.ENTER_FRAME, onScale, false, 0, true);
  26. }
  27.  
  28. function onScale(evt:Event):void {
  29.     var mc:MovieClip = MovieClip(evt.target);
  30.     mc.scaleX += (mc.scaleDest - mc.scaleX) / 4;
  31. }
  32.  
  33. function onOver(evt:MouseEvent):void {
  34.     evt.target.scaleDest = 3;
  35. }
  36.  
  37. function onOut(evt:MouseEvent):void {
  38.     evt.target.scaleDest = 1;
  39. }
  40.  
  41. addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  42.  
  43. function onLoop(evt:Event):void {
  44.     // loop through all boxes and position them
  45.     for (var i:int = 1; i <boxNum; i++) {
  46.         var b:MovieClip = MovieClip(nav.getChildAt(i));
  47.         var prev:MovieClip = MovieClip(nav.getChildAt(i-1));
  48.         b.x = prev.x + prev.width;
  49.     }
  50. }

We've reduced the amount of work needed to change the number of boxes down to the variable boxNum. On line 6 we create a movie clip and store a reference to it in a variable called nav. We place all our boxes within this clip starting on lines 13-18. It's important to note that we've set the Box movie clip in our library to export for ActionScript.

The real trick is the way we loop through and position all the boxes:

for (var i:int = 1; i < boxNum; i++) {
    var b:MovieClip = MovieClip(nav.getChildAt(i));
    var prev:MovieClip = MovieClip(nav.getChildAt(i-1));
    b.x = prev.x + prev.width;
}

Within our for..loop we have references to the current clip b and the previous movie clip prev. We start the loop counter (i) at one so b is the second box and prev is the first box. The next time the loop runs b is the third box and prev is the second, and so on. The final result looks like this:

This movie requires Flash Player 9. Please update your player.

Download: align_nav_2.fla  Download align_nav_2.fla (5.7 KB, 149 hits)

As an exercise you might consider building on this code to create an accordion widget.

Advanced Align Example

A more advanced version of this alignment allows the user to drag and drop the boxes. This technique could be used to create a myriad of different Flash widgets. I've used it to create functionality similar to photoshop layers and primitive timelines like the ones you see in video and audio editors.

This movie requires Flash Player 9. Please update your player.

Download: advanced_algin.fla  Download advanced_algin.fla (7.8 KB, 184 hits)

This is what the code looks like:

ActionScript 3.0:
  1. var boxNum:int = 8;
  2.  
  3. var boxes:Array = new Array();
  4.  
  5. var container:MovieClip = new MovieClip();
  6. container.x = 50;
  7. container.y = 200;
  8. addChild(container);
  9.  
  10. for (var i:int = 0; i <boxNum; i++) {
  11.     var b:MovieClip = new Box();
  12.     b.x = 50 + b.width * i;
  13.     b.originalIndex.text = i;
  14.     setupDraggableBox(b);
  15.     container.addChild(b);
  16. }
  17.  
  18. function setupDraggableBox(mc:MovieClip):void {
  19.     // local variable for use with zeno's paradox
  20.     mc.xDest = mc.x;
  21.     // randomize the width to illustrate flexibility
  22.     mc.bg.width = int(Math.random() * 30) + 30;
  23.     mc.buttonMode = true;
  24.     mc.mouseChildren = false;
  25.     mc.addEventListener(MouseEvent.MOUSE_DOWN, onDown, false, 0, true);
  26.     boxes.push(mc);
  27. }
  28.  
  29. function onDown(evt:MouseEvent):void {
  30.     removeEventListener(Event.ENTER_FRAME, onLoop);
  31.     container.addChild(evt.currentTarget);
  32.     evt.currentTarget.startDrag();
  33. }
  34.  
  35. stage.addEventListener(MouseEvent.MOUSE_UP, onStageUp, false, 0, true);
  36.  
  37. function onStageUp(evt:MouseEvent):void {
  38.     stopDrag();
  39.     addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  40. }
  41.  
  42. addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  43.  
  44. function onLoop(evt:Event):void {
  45.     // sort the boxes array by the x property
  46.     // of each clip
  47.     boxes.sortOn("x", Array.NUMERIC);
  48.  
  49.     // make sure the first box always eases to
  50.     // the same position (50, 200);
  51.     boxes[0].x += (0 - boxes[0].x) / 2;
  52.     boxes[0].y += (0 - boxes[0].y) / 2
  53.     boxes[0].currentIndex.text = 0;
  54.    
  55.     // align boxes
  56.     for (var i:int = 1; i <boxNum; i++) {
  57.         var b:MovieClip = boxes[i];
  58.         var prev:MovieClip = boxes[i - 1];
  59.         b.currentIndex.text = i;
  60.         b.xDest = prev.x + prev.width;
  61.         b.x += (b.xDest - b.x) / 2;
  62.         b.y += (0 - b.y) / 2;
  63.     }
  64. }

The basic structure of this code is similar to the prevous examples with a few important differences. All the boxes are stored in an array. On line 47 we call the Array.sortOn() method which sorts the boxes array according to the x position of each box. So the box with the lowest x position will become boxes[0]. Once the array is sorted we're able to run our align code (lines 56-63), which works almost exactly the same as it did in the previous examples. The only difference is that we set the local variable xDest that exists within each box instead of directly setting each box's x property. xDest is then used in conjunction with Zeno's paradox.

Share This:
  • Digg
  • del.icio.us
  • Netvouz
  • DZone
  • ThisNext
  • MisterWong
  • Wists
  • blogmarks
  • BlogMemes
  • Fark
  • feedmelinks
  • Furl
  • Ma.gnolia
  • Netscape
  • Reddit
  • Slashdot
  • SphereIt
  • Spurl
  • StumbleUpon
  • Technorati
  • YahooMyWeb
  • BlinkList
  • DotNetKicks
  • LinkaGoGo
  • NewsVine
  • blinkbits
  • co.mments
  • MyShare
Print This Post Print This Post

Related Content



8 Responses to “The Power of Relative Positioning”


  1. 1 Alan May 14th, 2008 at 3:09 am

    Nice werk. I look forward to playing with this.

    Thanks

  2. 2 Keith H May 14th, 2008 at 10:16 pm

    That was inspiring. Help me to think in a more creative way

  3. 3 Zevan May 14th, 2008 at 10:32 pm

    Thanks Alan and Keith. Glad to hear that it helped you to think about programming in a creative way.

    There's lots of fun to be had with the techniques mentioned in this post.

  4. 4 Dimitris May 14th, 2008 at 11:08 pm

    keep on the good work... 2 thumbs up!

  5. 5 Alex Jul 4th, 2008 at 2:36 am

    Beautiful job.

  6. 6 Ervin Jul 11th, 2008 at 10:17 am

    Thank!! It's Good job!

  7. 7 thon Jul 18th, 2008 at 11:25 pm

    Question: Why is the variable scaleDest declared simply
    mc.scaleDest = 1;

    Why in this case is var scaleDest:Number = 1; not used?

  8. 8 Rich Jul 19th, 2008 at 2:59 am

    @thon, good question. If you're referring to line 10 of the first script, it's because mc isn't a variable. It's an argument so it is valid while inside the function and does not require a declaration. It's easy to miss, but if you need a brush up on this, take a look at Chapter 2 in the book in the section called Functions. Good luck!

Leave a Reply