Initialization

To initialize the menus for animation, the following code is placed in the header of the document:

  1. <script type="text/javascript">
  2.   $(document).ready(function() {
  3.     jldAnimation({duration:300, effect:'slide'}, 'navH', 'navV');
  4.   });
  5. </script>

The first argument passed to this function is a simple object containing options for the animation. None of the options are required, in which case the object can be empty. Otherwise, it can be used to override the following defaults:

  1. {
  2.   duration: 500,
  3.   effect: 'slide', //can be 'slide', 'portal' or 'default'
  4.   sliverW: 5 //width of the initial portal sliver; only applies to 'portal'
  5. }

Note that sliverW is only needed for the 'portal' effect, and without incorporating a delay in the hover events the 'portal' effect can become unstable. If you want to experiment with it, go ahead. But don't use it on a web site without the proper delay, as discussed later in this tutorial.

Remember that JavaScript arrays are zero-based, so the first argument in the arguments array (arguments[0]) is the object described above for defining optional parameters. Arguments 1... and higher are the classes assigned to the <div> tags containing each main menu; in this case navH and navV. Any number of menus can be animated with a single call to the initialization function, though if separate options are desired for different menus, then separate calls should be made so a different function object will be created for each set of options. Be sure to not include the leading "." character in the menu class strings passed to this function. Below is the initialization code of the main function:

  1. var duration = 500;
  2. var effect = "default";
  3. var sliverW = 5;
  4. if (opts.duration) duration = opts.duration;
  5. if (opts.effect) effect = opts.effect;
  6. if (opts.sliverW) sliverW = opts.sliverW;
  7. var hideDuration = duration/2;
  8. var activeLI;
  9. var menus = [];
  10. for (var i = 1; i < arguments.length; i++) {
  11.   var nav = $("." + arguments[i]); //get the nav element containing the main class
  12.   if (nav.hasClass(arguments[i] + "show"))
  13.     nav.removeClass(arguments[i] + "show");
  14.   menus[i - 1] = {
        cls: arguments[i],
        div: nav,
        topUL: nav.children("ul")
      };
  15.   nav.find("a").hover(aORliEnter, aORliLeave);
  16.   nav.find("li").hover(aORliEnter, aORliLeave);
  17.   menus[i - 1].topUL.hover(ulEnter, ulLeave);
  18.   menus[i - 1].topUL.children("li").children("ul").each(function () {
        $(this).css("display", "block");
        storeDimsAndRecurseTree($(this));
        $(this).css("display", "none");
     });
  19. }

This code does the following:

  1. Lines 1-7 declare local variables and defaults for the options, and load any overrides provided.
  2. Line 9 sets the duration for hiding elements to half that for showing them, strictly a design choice.
  3. Line 11 declares a variable to hold the most recent <li> hover element, which can be undefined.
  4. Line 12 sets up an empty array to hold some basic information about each main menu.
  5. Line 14 iterates through arguments 1 and up.
  6. If foo is passed as one of the arguments, line 15 finds the <div> that contains the class="foo" class reference.
  7. Lines 16 and 17 remove the class="fooshow" reference so DHTML no longer displays the submenus.
  8. Lines 18 store the class string, the jQuery <div> element, and the top jQuery <ul> element in the "menus[]" array.
  9. Lines 19 and 20 assign hover enter and leave functions to all of the <a> and <li> elements in the menu; see the discussion below.
  10. Line 21 assigns hover enter and leave functions to the topmost <ul> element (the main menu) in the menu structure.
  11. Lines 23 store some relevant data required for the animation.

It's not absolutely necessary to assign enter and leave events to both the <a> and <li> elements, as is done here in code lines 19 and 20. When entering an <a> element, the <a> element always gets an enter event, but the same is not true of the <li> element that contains it. The <li> element gets an enter event only sporadically. So assigning events to the <a> elements is a must. But if enter events are assigned only to the <a> elements, no hover event is fired when the cursor hovers over the padding or border of an <li> element. Assigning to both covers all bases, but that also means the enter event can be fired twice for a single entry. The activeLI variable is used to insure that a second 'enter' event doesn't attempt to show the menu a second time. Look closely at the code for the activeLI variable to see how this is handled.

To see the effect of Lines 16 and 17, this example page is identical to the previous page, but Lines 16 and 17 have been commented out so the class references to navHshow and navVshow are not removed. Try it out. The animation is completely disabled.

In fact, the animation is not disabled. It has been initialized, and is fully functional, but dynamic HTML initiates an immediate hover trigger when the mouse cursor hovers over a menu item, and with the display rules still in place, dynamic HTML displays the menu immediately. So when the animation kicks in and tries to display the menu, it's already visible and nothing happens.

Removing the navHshow and navVshow class references removed the rules that displayed the submenus, allowing the animation to take over. The animation could be designed without removing the display rules. However, it would have to go to some trouble to insure that submenus didn't flash momentarily visible because of the behavior of dynamic HTML.

Lines 23 also deserve some comment. To get the computed css values that will be manipulated, the elements must be visible. Otherwise the values are not computed, and the only values returned are zeros. But setting the "display" property of a <ul> element to "block" doesn't make it visible unless it's parent is visible. This code snippet makes each <ul> element visible and gets the needed values. Then it recurses downward through the menu tree, making each child element visible and getting their values. After it reaches the bottom of the tree, it resets the display property of each element as it backs out of the menu tree.

Functions for showing and hiding submenus

Submenus are hidden and shown for the "slide" effect using the JQuery "animate" function, and for the "default" effect using the JQuery "show" function. To compare the two, view this example page in which the horizontal menu uses the "slide" effect, and the vertical menu uses the "default" effect. To make it easy to view the effects and their differences, the duration has been set to 3 seconds for this example.

The JQuery "show" function animates the height, width and transparency simultaneously. The "slide" function animates the height and top margin for dropdown, or width and left margin for flyright, to give the impression that the submenu is sliding out from under its parent. Here is pseudo code for the "slide" effect in the "showSlide" function:

  1. var dims = getAnimDims(targetUL);
  2. var drop = isDropDown(targetUL);
  3. var wORh = drop ? "height" : "width";
  4. var lORt = drop ? "margin-top" : "margin-left";
  5. var props = {};
  6. props[wORh] = "show";
  7. var opts = {duration: aDuration};
  8. opts.step = function (now, fx) {
  9.   if (drop)
  10.     $(this).children("li").first().css(lORt, -(fx.end-now));
  11.   else
  12.     $(this).children("li").css(lORt, -(fx.end-now));
  13. }
  14. opts.complete = function () {targetUL.css("overflow", "visible");};
  15. targetUL.animate(props, opts);

This code is referred to as "pseudo" code becasue the actual code is a bit more compact, whereas the code above breaks out the "step" function into an "if" statement for clarity. It does the following:

  1. Line 1 retrieves local computed CSS values for "height", "width", "margin-top" and "margin-left". Take a look at the getAnimDims function. When hiding a submenu, these values are needed to reset these dimensions since the hide function scrambles them a bit.
  2. Lines 2-4 determine if it's a "dropdown" or "flyright" submenu, and set some variables accordingly.
  3. Lines 6 and 7 set up a properties object to tell the "animate" function which property to animate and whether to show or hide it.
  4. Line 9 sets up an options object to tell the "animate" function the duration of the animation.
  5. Lines 10-15 set up a callback function that is called at each step of the animation. This is necessary since multiple elements are being animated: the width or height of the submenu <ul>, and the left or top margins of one or more of the <ul>'s submenu <li>'s.
  6. Line 16 sets up a callback function to reset the appropriate properties after the animation is complete. This is necessary when hiding a submenu because the margin value at the end of the animation is a large negative number, and it needs to be restored. Also note that resetting the "overflow" property to "visible" is only necessary in IE 6. It appears that jQuery handles this in all browsers but IE 6: possibly a bug in jQuery.

Note that the "hideMenuUL" function is almost identical to this "showMenuUL" function, except it reverses the animation of margins in the "step" callback.

aORliEnter function

The aORliEnter function is called when the mouse cursor hovers over any <a> or <li> element and it does the following:

  1. It first tests the hover element to determine if the event is the redundant second call and exits without doing anything if it is.
  2. It sets the "activeLI" variable to the appropriate value.
  3. If the new hover <li> is the parent of the old hover <li>, or even a distant ancestor, it hides the second level submenu beneath the new hover <li>, doing so with animation. It also insures that children of this second level submenu are properly hidden. And since the new hover <li>'s submenu is already visible, it exits from the function without doing anything else.
  4. If the new hover <li> is not the parent of the old hover <li>, it makes sure that all submenus of sibling <li>'s are hidden.
  5. It tests to see if the hover <li> has a submenu, and if so it:
    1. shows the submenu
    2. makes sure all deeper level submenus are hidden

aORliLeave function

The aORliLeave function does nothing, and is just a place-holder.

ulEnter function

The ulEnter function does nothing, and is just a place-holder.

ulLeave function

The ulLeave function is called when the mouse cursor leaves the main menu <ul> element. Since all submenus are contained within the main menu, this event only occurs when the cursor has completely left the menu tree. It hides all submenus of the top <ul> element. This is one of the most robust ways of handling the situation where the cursor completely leaves the menu.

Hiding submenus

Submenus are hidden with animation by reversing the animation used to show them. However, they're hidden in half the time. This was a design choice and it can be seen in action more clearly in the previous example page in which the duration has been set to 3 seconds.

Conclusion (almost)

These menus are robust, work across browsers, and in older browsers. However, there's one thing still missing, and to see the problem it creates, go to this example page and follow the instructions on the page.

Let's go to the next and final tutorial to fix it.

Animation:  A-1  [A-2A-3  next