Building a Mega-Navigation Menu with CSS3 and jQuery

by Jake Rocheleau

May 5, 2014 in Tutorials

Larger navigation menus will typically include separate dropdown lists to appear on hover. But for some websites it can be necessary to expand this dropdown across the entire page. Designers might call this a “mega navigation” for its influence over the whole menu.

In this tutorial I want to demonstrate a method of creating one unified mega navigation menu. There are many different techniques you can use to achieve a similar effect. I’ll be using jQuery to embed sub-navigation content into a dropdown mega nav box. Take a look at my live demo to see the final design.

mega navigation menu howto tutorial preview jquery

Getting Started

The first step is to download a copy of jQuery to include in the main document. I’ve created a simple stylesheet which we can get into after the HTML. The page layout itself is wrapped in the center with a small header section.

After this header it’s fairly obvious that the navigation menu is large and takes up space. The container nav element uses an ID of #menu which spans the entire width of the page. But the content is centered using the .wrap class. Within this wrapper is another ID #navbar which is applied to an unordered list of links.

<nav id="menu">
  <div class="wrap">
    <ul id="navbar" class="clearfix">
      <li class="first"><a href="#">Home</a></li>
      <li><a href="#">Products<span class="arrow"></span></a>
        <div class="megacontent">
          <div class="col">
            <h3>Tech</h3>
            <ul class="navlist">
              <li><a href="#">PC Cases</a></li>
              <li><a href="#">Hard Drives</a></li>
              <li><a href="#">Memory</a></li>
              <li><a href="#">Sound Cards</a></li>
              <li><a href="#">Graphic Cards</a></li>
              <li><a href="#">Wireless Mice</a></li>
              <li><a href="#">Wireless Keyboards</a></li>
            </ul>
          </div>
          ...

Each list item contains one primary navigation element. Secondary elements are considered mega nav structures wrapped in a div tag. We could include a long list of links, or we could embed a video.Any type of content may be added into the mega navigation regardless of the unordered list structure.

It’s all contained within a class .megacontent and never gets displayed until it’s loaded into the dropdown menu. This is located much lower down beneath the entire navigation. You’ll find an empty div with the ID #megamenu and this will be the container for each submenu’s dropdown.

CSS Styles

I’ve used a separate stylesheet named styles.css which includes a few resets and some layout positioning. You’ll also notice I’ve included a separate Google Web Font called Nova Square. Most of this code is rudimentary design including the background-size effect for the photo heading.

/** page structure **/
.wrap {
  display: block;
  width: 850px;
  margin: 0 auto;
}

#top {
  display: block;
  width: 100%;
  height: 60px;
  background: #fff;
}

#fullimg {
  display: block;
  height: 350px;
  background: url('../img/leader-sm.jpg') no-repeat;
  background-size: contain;
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-radius: 5px;
  -webkit-box-shadow: 2px 2px 1px rgba(0,0,0,0.3);
  -moz-box-shadow: 2px 2px 1px rgba(0,0,0,0.3);
  box-shadow: 2px 2px 1px rgba(0,0,0,0.3);
}

#page {
  display: block;
  padding: 20px 0;
}

A bit further down is the mega navigation structure. Since #menu spans the entire page width it will be filled using a CSS3 gradient. This way the content is aligned to the center but we still get this design effect regardless of browser size.

/** navigation **/
#menu {
  display: block;
  position: relative;
  width: 100%;
  height: 55px;
  margin-bottom: 15px;
  background-color: #383838;
  background-image: -webkit-gradient(linear, left top, left bottom, from(#383838), to(#282828));
  background-image: -webkit-linear-gradient(top, #383838, #282828);
  background-image: -moz-linear-gradient(top, #383838, #282828);
  background-image: -ms-linear-gradient(top, #383838, #282828);
  background-image: -o-linear-gradient(top, #383838, #282828);
  background-image: linear-gradient(top, #383838, #282828);
}

#navbar {
  display: block;
  list-style: none;  
}
#navbar li {
  display: block;
  float: left;
}

#navbar li a {
  display: block;
  position: relative;
  float: left;
  line-height: 55px;
  padding: 0 10px;
  font-size: 1.4em;
  color: #ddd;
  font-weight: bold;
  letter-spacing: -0.02em;
  text-decoration: none;  
}
#navbar li.first a {
  padding-left: 0;
}
#navbar li a:hover, #navbar li a.open {
  color: #fff;
}
#navbar li a.open {
  background-color: #282828;
  background-image: -webkit-gradient(linear, left top, left bottom, from(#282828), to(#181818));
  background-image: -webkit-linear-gradient(top, #282828, #181818);
  background-image: -moz-linear-gradient(top, #282828, #181818);
  background-image: -ms-linear-gradient(top, #282828, #181818);
  background-image: -o-linear-gradient(top, #282828, #181818);
  background-image: linear-gradient(top, #282828, #181818);
}

Each of the list items will float along with the anchor links. It is essential to include a clearfix on the unordered list to keep everything recognized within the document structure.

When hovering onto a link it’ll automatically lighten the font color. But if we detect it has some mega navigation content the link also gets an appended class of .open. This changes the background color to stand out from the rest of the menu.

/** mega navigation **/
.megacontent {
  display: none;
}

#megamenu {
  display: none;
  position: relative;
  top: 0;
  width: 850px;
  height: auto;
  padding: 15px 10px;
  background: #fff;
  z-index: 999;
}

#megamenu .col {
  float: left;
  min-width: 120px;
  margin-right: 10px;
}

#megamenu .col h3 {
  display: block;
  font-size: 1.55em;
  font-weight: bold;
  color: #5e6699;
  margin-bottom: 6px;
}

#megamenu .col .navlist li a {
  display: block;
  width: 100%;
  color: #515463;
  font-size: 1.2em;
  padding: 4px 2px;
  text-decoration: none;
}
#megamenu .col .navlist li a:hover {
  background: #e5e6ec;
}


#megamenu .postlist {
  width: 100%;
}
#megamenu .postlist li {
  display: block;
  position: relative;
  float: left;
  margin-right: 10px;
}

#megamenu .postlist li h4 {
  position: absolute;
  bottom: 15px;
  left: 10px;
}
#megamenu .postlist li h4 a {
  padding: 6px 5px;
  color: #eee;
  font-size: 1.35em;
  font-weight: bold;
  text-decoration: none;
  background: #323232;
}

The block of code above is most important for getting everything to display properly. Both the .megacontent and #megamenu divs are not displayed on the page. But #megamenu is the only one which does get displayed, since all the navigation content is imported into that div. .megacontent is more of a container which most users never get to see, but it’s good for old phones and screen readers to have the menus close together.

The .col stands for “column” and this is used to create multi-column spans across the menu. No matter how much content is used, the mega navigation dropdown will span the entirety of the inner page. This is good for consistency and it helps the menu appear bigger on-screen.

Capturing Hover States with jQuery

There’s a few checks we need to make regarding the hover state, but this script only has two primary actions. First we determine if the hovered link contains mega navigation content, and if so then we copy it into the #megamenu div.

The second action is to display the menu and detect once the user is done hovering so we can hide it again. You should have some understanding of event handlers before modifying the code too much.

$(function() {
  var $megabox     = $('#megamenu');
  var fadeinspeed  = 200;
  var fadeoutspeed = 240;
  
  $('#navbar li a').on('mouseenter', function(event){
    if($(this).next().attr('class') == 'megacontent') {
      var mmenu = $(this).next().html();
      $megabox.html(mmenu);
      
      $(this).addClass('open');
      $megabox.fadeIn(fadeinspeed);
    }
  }).on('mouseleave', function(event){
    var newelm = event.toElement || event.relatedTarget;
    var $link = $(this);
    
    if($(newelm).attr('id') != 'megamenu') { // if newly hovered elm isn't megamenu
      $link.removeClass('open');

      if($(newelm).next().attr('class') != 'megacontent') { // if newly hovered elm isn't a megamenu link
        $megabox.fadeOut(fadeoutspeed);
      }
    }
  });

The variable $megabox will hold the jQuery element so it doesn’t need to be repeatedly targeted in the DOM. Also I’m setting the fade-in and fade-out speeds through variable names which you can easily update. The event handler is targeting every anchor link within the #navbar for mouseenter events(basically hovering).

But there is a slight distinction between hover and mouseenter – specifically that mouseenter changes to mouseleave when not hovering anymore. We need to capture both events and deal with the mega nav menu accordingly. If the hovered link contains an internal .megacontent then we know it needs to display a new menu.

I’ve merely copied the .megacontent into our primary #megamenu with the jQuery html() method. Then using fadeIn() the effect is complete.

Chaining Hover Events

The two variances mouseenter and mouseleave can be tied onto the same target. You’ll notice I have two .on() methods with both events triggering different blocks of code. When leaving the link we need to make sure the user isn’t hovering onto the mega nav itself.

Both of the properties event.toElement and event.relatedTarget can be pulled from the event variabile. This is automatically created during an event handler and the properties change whether it’s a hover, click, scroll, or something else.

$('#navbar li a').on('mouseenter', function(event){
  // when mouse is hovering a link do something
}).on('mouseleave', function(event){
  // when the mouse is gone do something else
}

This is what my event handler would look like without any code. It’s one big jQuery block yet very easy to understand once you break it down into the primary functions.

Hiding the Mega Menu

We know that hovering off the link elsewhere on the page will hide the menu, unless hovering onto the menu itself. So now we need to check when the user is hovering the mega menu and then hovers elsewhere on the page.

What if they hover back onto the primary link? In that scenario we should keep the menu visible, otherwise it needs to be hidden.

  $($megabox).on('mouseleave', function(event){
    var newelm = event.toElement || event.relatedTarget;
    var $link = $('#navbar li a.open');
    
    if($(newelm) != $link) { $link.removeClass('open'); }

    if($(newelm).next().attr('class') != 'megacontent') {
      $megabox.fadeOut(fadeoutspeed);
    }
  });

  $('body').on('click', 'a[href^="#"]', function(event){
    event.preventDefault();
  });
});

I’m using a very similar function to check if the hovered item isn’t the primary nav link. It’s basically the same as before except checking for a different hover element when the user moves their mouse off the menu.

At the bottom of my code you’ll notice a snippet which prevents any link with the href=”#” attribute from loading. In most cases it will cause the website to jump up towards the top of the page and it’s an ugly effect in tutorials. You’re free to remove this block of code or keep it – but it should only block links which have the empty hash symbol and nothing more.

mega navigation menu howto tutorial preview jquery

Closing

Not every website project is in need of these mega navigation menus. I typically see them on magazines or larger websites using multiple categories and styles of post content. It’s fantastic at capturing interest and drawing a closer user experience. Feel free to download a copy of the source code and see what other styles of navigation you can build.

Powered by Shutterstock

About Jake Rocheleau

Jake is a digital researcher and writer on many popular design magazines. He frequently writes on topics including web design, user experience, mobile apps, and project management. You can find him all throughout Google and tweeting @jakerocheleau. Connect with Jake on google+

It's pretty quiet on this post. Why not share your thoughts?

Leave a Comment

Your email address will not be published. Required fields are marked *