Monday, December 2, 2013

Dynamic Collapsible Menus Based on Page Scroll Position

Navigation menus can eat up page real estate pretty quickly. While the user is interacting with the navigation, it makes sense to use the space necessary to allow them to make a selection. However, once they start to scroll into the content of the page, that navigation bar can become a nuisance. A lot of sites have started using the scroll position of the page as an indicator that the user has stop navigating and is now focused on the page content. As the scroll occurs, the navigation bar collapses into a compact representation of the menus that still allow navigating, but provide either less options or less verbose labeling. Google+ is good examples of a site that employs this technique and I was interested in creating a similar effect myself.

I decided to start with my jQuery UI Scrollable widget to monitor a certain element to determine when to toggle between the full and collapsed navigation menu. The full example presented here is one of the Scrollable widget's demo pages. The reason I chose this route is because the widget conveniently triggers one event when a target element is inside or outside the defined view port:


$(function () {
   $search = $( '.nav-search' );

   $('.menu')
      .on('scrollin', function ( e, ui) {

            ...

         })
      .on('scrollout', function ( e, ui) {

            ...

         })
      .scrollable({ offset: { top: 50 } });

});


For simplicity, I chose to construct my sample navigation menu with Bootstrap. The goal is to create a bar that looks like this:



When scrolled to the top of the page and looks like this:



Once the user starts scrolling into the content. The markup to achieve this effect contains two separate Bootstrap navbar element blocks:



The trick is to get the bottom navbar to slide under the top navbar and, once out of view, the search box appears over the top navbar. As it turns out, this is not as easy as I thought it would be. The Bootstrap navbars have z-index values and a mix of fixed/relative positioning. This took some research to realize how to get a child element's z-index to be effective outside of a parent's z-index. Here's the important lines of HTML that need to be considered:



  • navbar-fixed-top has fixed positioning and has a z-index of 1080
  • navbar-static-top is positioned relative and has a z-index of 1000
  • nav-search is a child of the static navbar and needs to remain under the fixed navbar until the static navbar is out of view
I didn't want to move the search box between parents. In many cases, I have an MVC library managing the content and events in the page. Moving elements around starts causing problems and adding complexity I'd rather avoid. Instead, I wanted to find a solution that left the elements in their original locations in the DOM but style them to toggle into the correct place when scrolled.

In one attempt, I simply tried to change the z-index of the nav-search to be less/more than the fixed navbar. However, when inside a relative parent, the child's z-index is only relative to its parent, not the entire page. While I could make the navbar statically positioned, I'm hesitant to do this while the navbar is visible since other Bootstrap styles may assume the relative parent and not render properly. However, once the navbar scrolls out of view, I have no problem switching to that setting since it doesn't matter what it looks like when you can't see it. So the final toggling that causes the bottom navbar to scroll under the top navbar and only show the search box over the top navbar once the bottom bar is out of view is:


$(function () {

 ...
      .on('scrollin', function ( e, ui) {

            $search.css({
                position: 'absolute',
            }).parent().css( 'position', 'relative' );

         })

      .on('scrollout', function ( e, ui) {

            $search.css({
                position: 'fixed',
            }).parent().css(  'position', 'static' );

         })
  ...
});


Since the z-index is already set on the search box, it will pop over the top navbar as soon as its parent toggles to a static position. The added fixed position on the search box enables it to float in the correct place regardless of where the page is scrolled. I also shifted the search box to the left and adjusted its width slightly to allow it to fit properly in the top navbar.

With this basic concept worked out, you can add more menu items and shift or even transition items around as the user scrolls the page.