Scrolling in Navigation: A Parallax Menu


So far we have covered parallax scrolling through both manual tweening and ActionScript. Although the last example was somewhat dynamic, the scrolling wasn't completely controlled by user interaction; it was only affected by it. In this example, you will create a navigation system that scrolls based on the user's mouse position. As you delve further into the structure, top levels appear to recede on the stage, which should naturally affect their scrolling rate. Look at the file parallaxMenu_end.swf to see the final result.

click to expand

Setting Up

  1. Open the file parallaxMenu_start.fla and peruse its contents. Although you will be writing most of the code to create the ending navigation, there are a few pieces already prepared for you. First, the background gradient has been drawn on the locked background layer. Feel free to adjust these colors (and any other colors in this project) to your liking. The only other layer is the XML layer, which contains the code necessary to bring in and parse an XML file containing your menu information. XML, or eXtensible Markup Language, is a tagging language used to create structured documents, allowing data to be shared between applications. Since covering XML is beyond the scope of this chapter, we've taken care of all the code needed to deal with the XML file.

    Finally, the Library has a single symbol, NavButton, and this is the only symbol that will be used in this movie. This symbol doesn't contain much code, and the code has nothing to do with the scrolling effect, so it has also been prepared for you. All that the symbol does is draw a filled text field with a specified string of text, adds a faint trapezoidal shadow beneath the text field, and turns on the text field's border when the mouse rolls over it. The button's actions and its placement in the scrolling rows are controlled by the external script that you'll be writing in future steps.

    click to expand
  2. Open up the file menus .xml in your favorite text editor to see how the XML file is prepared with your menu information. Make sure this file is saved into the same directory as parallaxMenu_start.fla. Here's a truncated excerpt of the script:

     <?xml version="1.0" encoding="iso-8859-1"?> <menus>   <levell title="home">     <level2 title="news">       <level3 title="current" />       <level3 title="archive" />     </level2>     <level2 title="links">       <level3 title="business">         <level4 title="domestic" />         <level4 title="international" />       </level3>       <level3 title="personal" />       <level3 title="others" />     </level2>       .       .       .   </level1> </menus> 

    The root node of the menus is appropriately named <menus> . Each nested node is named <level n > and has at least one attribute, title . This is the name that will appear on the button. If a node does not have any children (such as home > news > current , above), then it should contain another attribute named url with a value that you want passed to the Flash movie, whether it be an actual URL, a Flash movie to load, or perhaps the identifier name of a symbol that you want to attach from the Library. It all depends on how you set up the navigation system in Flash (which you will do in a moment). For the purpose of this tutorial, we have left out the URL attribute since you're more interested in the scrolling of the system, rather than actually loading any content. But suppose you used this system to load a Flash movie into the Flash interface whenever you clicked a button at the bottom of the nested menu. In that case, you might write the home > news > current node as follows :

     <level3 title="current" url="currentNews.swf" /> 

    When the XML is loaded into your Flash movie, the code on the XML layer of the main timeline parses the file and places all the menus' information into an array named sections . To see this in action, and how sections is structured, you'll test the movie with the Flash MX Debugger.

  3. Return to parallaxMenu_start.fla and create a new layer above the XML layer. Name it set up , then add this code to its first frame:

     xmlFile = "menus.xml"; 

    This lets the XML object created in the XML layer know which file to load into the movie.

    click to expand
  4. Test your movie with the Debugger window by using the menu command Control > Debug Movie (or by pressing CTRL/CMD+SHIFT+ENTER). When you enter the testing environment, click the green play button in the Debugger window. Your XML should load silently in the background. To see how it populated the sections array, select _lev el0 in the top left of the Debugger, and then click on the Variables tab in the center left of the Debugger panels.

    click to expand

    You should see $version, xmlFile, and NavButton listed in the V ariables tab, along with sections, which has a plus sign next to it. Click on this plus sign to expand sections, and you should see nested indices ( 0, 1, 2 ) with plus signs next to each. You can expand each index to see what's inside. Basically, each index has three properties: name, url, and children. Its children property is another array that holds all of its children objects, or subsections. Each of these in turn has up to three properties. This continues down into sections until the bottom of the stack for each section is reached.

Phew! Now that you've had a glimpse of how you create your sections outside of Flash, as well as how you structure that information inside of Flash, it's time to actually do something with that information.

Button Formatting

The first thing you need to do is decide on some parameters for your menu buttons . You will create some variables in the set up code layer that will allow you easy access to tweaking later on.

  1. In the set up layer, add the following lines of code:

      MovieClip.prototype.useHandCursor = 0;   stageWidth = 750;  xmlFile = "menus.xml";  buttonWidth = 250;   buttonHeight = 35;   unselectedColor = OxDBEBCD;   selectedColor = OxB3D396;  // space between each button  buffer = 30;   sectionTextFormat = new TextFormat();   sectionTextFormat.font = "Verdana";   sectionTextFormat.size = 25;   sectionTextFormat.align = "center";  

    The first line simply gets rid of the hand cursor as you mouse over a (movie clip) button. You then set a variable based on the width of the stage. After that, there are number of variables to affect the look of your buttons. Finally, you create a TextFormat object for the text on the buttons. We chose to use Verdana as the font, though you may change it to whatever you'd like (you might have to adjust the size).

  2. Create a new layer above the background layer and name it textfield . With the layer selected, draw a dynamic text field off the left side of the stage. It doesn't matter too much where you place it since it will be invisible to the user (it has no text in it). You are adding the text field to the stage so you have control over the embedding of the font, which will allow for smoother animation and consistency of design for all users.

    click to expand
  3. Deselect the new text field's Selectable option in the Property Inspector, and set its font to match the font you specified in the previous step's TextFormat (Verdana, in our case). To embed the font, click on the Character... button in the Property Inspector and select Only Uppercase Letters , Lowercase Letters , and Numerals . Then press Done . Again, you are embedding for two reasons: first, to keep your system visually consistent for all users, and second, because embedded fonts simply animate better on the stage.

    click to expand

Adding Rows

Now that you know what your buttons will look like, let's add your first row to the stage. In the XML layer, the parsing function ( parseXML ) calls another function once the sections array has been filled. The new function is called drawRow, and the top level of sections is sent to it initially so that the top level of buttons might be placed on the stage. Let's take care of that function now.

  1. Create a new layer on the main timeline named row code and place it below the XML layer. On the first frame of row code , enter the following script:

     // holds references to rows rows = []; currentRow = 0; 
    click to expand

    Before you write the drawRow function, you need to initialize some variables: rows will hold references to every row of buttons you place on the stage, and currentRow is the current index of that array, namely the first index.

  2. Add the following function after the preceding code:

     // draws row based on section sent drawRow = function(sections) {      // clip to hold row      var r = this.createEmptyMovieClip("row" + currentRow, XcurrentRow++);      rows.push(r);      // holds references to all of row's buttons      r.buttons = [];      var sectionsLength = sections.length;      // each button's _x based on width of entire row      var totalButtonWidth = (buttonWidth*sectionsLength) +    (buffer*(sectionsLength-1) ) ;      // attaches buttons for row      for (var i = 0; i < sectionsLength; i++) {        var b = r.attachMovie("NavButton", "btn" + i, i, Xsections[i]) ;        r.buttons.push(b);        b._x = -(totalButtonWidth/2) + (buttonWidth*i) + (buffer*i);      } }; 

    That's pretty simple to start off with, though you will add more as you go along. The first lines create an empty movie clip to hold all of a row's buttons. A reference to this movie clip is stored in the rows array. Then, after creating a new array property named buttons , which will store reference to all of the row's buttons, you loop through all of the section's children and add a button for each to the row. The most interesting aspect in the preceding code is that you send an initObject in the attachMovie ( ) call for the buttons. This sends a subsection's object (which includes name, url, and children as properties) to the newly created button. The button uses the name property to add to its text field, it will use the url property (if it has one) to load a page or a movie, and it will use the children property (if it has one) to create a subsection menu whenever it is pressed. All of that is done rather discreetly inside the attachMovie() call.

    Test your movie now to see the first row added to the stage. You have a little way to go before this effect is complete, but what a nice start! We will next look at tweening the row into a desired position on the stage.

    click to expand
  3. Add the following bold lines to the set up code layer:

     buttonWidth = 250; buttonHeight = 35; unselectedColor = 0xDBEBCD; selectedColor = 0xB3D396; // space between each button buffer = 30; // row attributes when first placed  startScale = 120;   startY = 400;  // factors for when a row moves up/back  yChange = 60;   yDiminish = .7;   scaleChange = 20;  sectionTextFormat = new TextFormat(); sectionTextFormat.font = "Verdana"; sectionTextFormat.size = 25; sectionTextFormat.align = "center"; 

    Here you are first positioning the row at the bottom of the stage ( 400 ) and at a scale of 120%. Then you will tween it into position ”the next three variables handle the numbers you will tween to. Basically, every row will move up 60 pixels initially and will lose 20% in scale. The yDiminish variable will be used to lessen the yChange value each time a row moves up ”this is a type of parallax scrolling. The rows that are closer in depth to the viewer will move more of a distance up the stage ("back") than the rows at the farther distances. This will go a long way to simulating a 3D menu system.

  4. Returning to the row code layer, add the following bold lines to the drawRow function:

     // draws row based on section sent drawRow = function(sections) {      // clip to hold row      var r = this.createEmptyMovieClip("row" + currentRow, currentRow++);      rows.push(r);      // initial properties  r._xscale = r._yscale = startScale;   r.yMove = yChange;   r._y = startY;  // holds references to all of row's buttons      r.buttons = [];      var sectionsLength = sections.length;      // each button's _x based on width of entire row      var totalButtonWidth = (buttonWidth*sectionsLength) +    (buffer*(sectionsLength-1));      // attaches buttons for row      for (var i = 0; i < sectionsLength; i++) {        var b = r.attachMovie("NavButton", "btn" + i, i, sections [i] );        r.buttons.push(b) ;        b._x = -(totalButtonWidth/2) + (buttonWidth*i) + (buffer*i);      }      // changes properties for each row to make room for new row  for (var i in rows) {   rows[i].newY = rows[i]._y - rows [i] .yMove;   rows[i] .yMove *= yDiminish;   rows[i] .newScale = rows[i]._yscale - scaleChange;  // each row set to tween into place  rows [i].onEnterFrame = tweenToPosition;   }  }; 

    When a row is first created, you set its initial properties before adding its buttons. Then, at the end of the function, you loop through all rows currently on stage and set the properties that they will tween to ( newY and newScale ). Then you set their onEnterFrame handler to a function named tweenToPosition . You'll write that next.

  5. Add the following function after the drawRow function:

     // animates row into new position tweenToPosition = function () {     this._xscale = this._yscale -= (this._yscale -this.newScale) /5;     this._y -= (this._y - this.newY) /5;     // once close enough, ends animation     if (Math.abs(this._yscale - this.newScale) < 1 && Math.abs(this.newY -    this._y) < 1) {       this._y = this.newY;       this._xscale = this._yscale = this.newScale;       this.onEnterFrame = scroll;     }     // makes row invisible at low scales     this._visible = this._xscale > 5 ; }; 

    This is a fairly standard easing function that moves and scales your row into position a little each frame. Once the row has reached its destination (or is close enough), its properties are set to its end values and the onEnterFrame handler is assigned a different function (which you'll tend to in a moment). You'll also notice that you make the row invisible if it is scaled to less than 5%.

    click to expand

Test your movie now to see the tweening into position. It is working for _y, _xscale , and _yscale , but you need to get it working for _x . That, however, will always be controlled by the user's mouse position.

The Sacared Scroll

Now that you have successfully added your first row and tweened it into its starting position, you need to write the code that will control its scrolling. You have used these same concepts of parallax scrolling in the previous projects, where the foreground objects move at a faster rate than the background objects. But here you will build on it further by dynamically deciding the depth of a row (and thus its scrolling rate) based on its present scale.

  1. Add the following new function to row code layer beneath the drawRow function (and above the tweenToPosition function):

     // scrolls each row scroll = function () {      // amount diminishes the farther away row is      // (thus at smaller scale)      var scrollAmount = scrollFactor*this._xscale/100;      // x-position changes based on xmouse      this._x = (stageWidth/2 - _root._xmouse)*scrollAmount + XstageWidth/2; }; 

Just a couple lines of code take care of so much! Jumping to the end first, you will see that you set the x-position of the movie clip (which will be an individual row) based on where the mouse is in relation to the center of the stage. For instance, if the mouse was at center stage (375 pixels), then this._x will equal 375, placing the clip right in the center of the stage as well ((375-375)x + 375). The farther the mouse moves to the right, the more the row will move to the left based on its scrollAmount .

But what is scrollAmount ? Well, as you can see, it is determined by the current _xscale of the row movie clip, so clips that are larger in scale (thus "closer" to the viewer) will have a larger scrollAmount and will scroll at a faster rate. We've also thrown in a new variable named scrollFactor to make it easy to change the relative scaling of the clips to affect the overall appearance of your menus. You'll set the variable in the next step.

  1. Go to the set up code layer and add the following bold line of code:

     // affects scrolling rate and distance  scrollFactor = 2;  // row attributes when first placed startScale = 120; startY = 400; 

    Now all you need to do is call the function when necessary. You've already set it to be called once a row is tweened into position, but you should also have it called while a row is being tweened, as well as when a row is first created.

  2. Head back to the row code layer and add the following bold line to the end of the tweenToPosition function:

     // makes row invisible at low scales      this._visible = this._xscale > 5;     // keeps scrolling the row as it animates  scroll.apply(this);  } 

    Here you use apply again (as you did in the Mars Patrol example) to apply the scroll function to an instance of a movie clip, meaning that the movie clip will be referenced by this inside the scroll function.

  3. Next, add the following bold line of code to the drawRow function:

     // initial properties r._xscale = r._yscale = startScale; r.yMove = yChange; // _x set  scroll.apply(r);  r._y = startY; 

    You apply the scroll function here as well so that the row may be placed at the proper _x-position upon instantiation.

Test your movie now to see the tweening and scrolling working like a charm .

click to expand

Spwaning Subs

This next, and final, function is probably the most complex that we've dealt with this chapter, but it's not so difficult to understand when broken down into parts . With it, you will enable your buttons to produce additional rows for their subsections. Once you do that, however, you'll have to move all previous sections up on the stage.

  1. Add these lines of a new function right after the drawRow and scroll functions in the row code layer. Note that this is not the completed function (also note that many of the following lines are comments, so don't feel too intimidated!):

     // called when a nav button is pressed buttonScript = function() {      // makes sure animation is occurring      for (var i in rows) {        if (rows[i].onEnterFrame == tweenToPosition) {return false};      }      // if this is front row      if (this._parent == rows[rows.length - 1]) {        // deselects all other buttons in row        for (var i in this._parent.buttons) {          this._parent.buttons[i].sectionName.backgroundColor =    unselectedColor;        }        // "selects" this button        this.sectionName.backgroundColor = selectedColor;        // if this section has no children, then it must        // have content to load        if (this.children ==  undefined  ) {          // write code to load content        } else {          // draw this section's children to new row          drawRow(this.children);        }      // this is a back row      } else { 

Don't test this just yet, as the preceding script is unfinished and so will generate errors. For clarity, let's break down this first part before you finish the function and get it working. This buttonScript will be called when any button in your navigation system is clicked, so it will need to know if the button clicked contains subsections, or information relating to a page or movie to load. The first thing you do in the function is run through all the current rows and make sure that none are currently animating. If any row is, then you exit this function. If, however, it is safe to proceed, you test to see if this row (the one with a button that was clicked) is the nearest row to the user by checking to see if this was the last row placed into the rows array. Only the nearest row can produce submenus, since any row farther back in the stack already has produced submenus.

OK, if you are indeed dealing with the front row of the stack, you deselect all of the buttons in the row (in case any are currently selected), and then select the button just clicked. Next, you look to see if this particular button doesn't have any children. If it doesn't, you know you've reached the bottom of the stack and you need to load a page or a movie. We haven't written the code that would do this, as it would change depending on the project, but this is where the code would be placed. If, however, children do exist for this button, then you need to add the new row by calling the drawRow function and sending it the array of children.

That's the brunt of it. Now you just need to take care of what happens when a button is clicked in a row other than the front.

  1. Add these lines after the code from in the previous step. These lines will remove all rows in front of the row just clicked, leaving room for all the rows to move up:

     // deselects all other buttons in row          for (var i in this._parent.buttons) {            this._parent.buttons[i].sectionName.backgroundColor =    unselectedColor;          }          // finds this row number          for (var i = 0; i < rows.length; i++) {            if (rows[i] == this._parent) {              // row in front of this row              var nextRowUp = i+1;              break;            }          }          // removes all rows in front of this row         for (var i = nextRowUp; i < rows.length; i++) {           rows[i].removeMovieClip();         }         rows.splice(nextRowUp); 

    After deselecting all of the buttons in the clicked row, you loop through the rows array to find the reference to the row just clicked. Remember that the rows that are the farthest back will be lower indices in the rows array, so once you find the row index, you add 1 to it to get the next row in the stack. It will be this row and all subsequent rows referenced in the rows array that will need to be removed, which you do in the next lines. Once the clips are removed, you also remove the references (which no longer apply) from the rows array by using the splice method.

  2. Here's the penultimate chunk of code. These next lines set the new positions for each of the rows based on each row's positions in the rows array. Add the following lines to the end of the buttonSelect function to finish it off:

     // runs through all remaining rows and sets properties        // to tween to based on position in rows array        var rowNum = 0;        for (var i in rows) {          rows[i].newScale = startScale;          rows[i].newY = startY;          rows[i].yMove = yChange;          var rowPosition = rowNum;          while (rowPosition > -1) {            rows[i].newScale -= scaleChange;            rows[i].newY -= rows[i].yMove;            rows[i].yMove *= yDiminish;            rowPosition--;          }          rows[i].onEnterFrame = tweenToPosition;          rowNum++;        }      } }; 

    After first reinitializing each position property for a row, you change each of these properties based on the row's index in the array. After the properties have been changed, you begin tweening into these new positions.

    Almost there! You now have a beautiful button script for your menu buttons, but you haven't yet actually assigned it to your buttons. You can do this with a single additional line in the drawRow function.

  3. Add the following bold line to the drawRow function to complete your scripting:

     for (var i = 0; i < sectionsLength; i++) {   var b = r.attachMovie("NavButton", "btn" + i, i, sections[i]);   r.buttons.push(b);   b._x = -(totalButtonwidth/2) + (buttonWidth*i) + (buffer*i);  b.onRelease = buttonscript;  } 
    click to expand

And you're done. Test your movie now (or take a look at parallaxMenu_end.fla ) to see your beautiful finished effect. Notice how each row scrolls differently based on its placement in the stack. This, combined with the scaling of the clips and the graphical shadows, gives a wonderful 3D feel to a navigation system that doesn't contain a smidgen of 3D code ”the ultimate 3D cheat!




Flash 3D Cheats Most Wanted
Flash 3D Cheats Most Wanted
ISBN: 1590592212
EAN: N/A
Year: 2002
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net