How to Build a Responsive Slide-Down Navigation Menu

by on 1st May 2013 with No Comments

featured preview jquery sliding toggle menu nav

After a long period of researching mobile responsive layouts, I’m spent quite a bit of time experimenting with various UI designs. One major hotspot on the page is often the website’s main navigation. Users want quick access to your content pages — and this will always be the case, either on a full monitor or a smaller mobile responsive screen.

For this tutorial I want to demonstrate how we can use a combination of CSS3 media queries along with some jQuery to manage a sliding navigation menu. The links will appear as normal on the frontend but drop into a hidden menu after resizing below 600px. Instead we see a little menu pull-down icon which will toggle open and closed on command.

Live DemoDownload Source Code

Setting the Document

The top heading area contains a small selection of files we need to create this effect. I am including a custom stylesheet along with a copy of the latest jQuery library hosted by Google. All of the custom JS functions are stored in an external file named menu.js.

<!doctype html>
<html lang="en-US">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
  <title>Responsive Sliding Navigation Demo</title>
  <meta name="author" content="Jake Rocheleau">
  <link rel="shortcut icon" href="http://designshack.net/favicon.ico">
  <link rel="icon" href="http://designshack.net/favicon.ico">
  <link rel="stylesheet" type="text/css" media="all" href="styles.css">
  <link rel="stylesheet" type="text/css" media="all" href="http://fonts.googleapis.com/css?family=Boogaloo">
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  <script type="text/javascript" language="javascript" charset="utf-8" src="menu.js"></script>
</head>

Most of the page markup is fairly unimportant aside from the header area. I have attempted to keep everything at a fixed height, but the header cannot use a fixed CSS property or else the page won’t expand when sliding the menu open and closed. So the heading and navigation links are setup by line-height values in CSS.

  <header id="topnav">
    <nav>
      <ul>
	<li><a href="#" class="sel">Home</a></li>
	<li><a href="#">About</a></li>
	<li><a href="#">Projects</a></li>
	<li><a href="#">Blog</a></li>
	<li><a href="#">Get in Touch</a></li>
     </ul>
    </nav>
    <a href="#" id="navbtn">Nav Menu</a>        
    <h1><a href="#">Designee</a></h1>
  </header><!-- @end #topnav -->

The page h1 title fits best as the last element so the header will always keep its constant height. Right next to the headline is an anchor link with the ID #navbtn and this contains the sliding menu link icon. It will only display using CSS after the width drops below a certain threshold.

Markup Positions in CSS

Typically I do not consider even requiring jQuery whenever it is possible. The only problem with toggling menus is that we cannot do this efficiently in CSS. And the jQuery methods will apply inline CSS styles which overrule the default stylesheet. So it comes down to positioning the elements using CSS and helping them animate properly with JavaScript.

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
  outline: none;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: none;
  -ms-text-size-adjust: none;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
html { height: 101%; }
body { 
  background: #f8f8f8 url('images/bg.png'); /* BG Neutral http://subtlepatterns.com/ps-neutral/ */
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 62.5%; 
  line-height: 1; 
  color: #343434;
  padding-bottom: 55px;
}

::selection { background: #b9e9b9; color: #555; }
::-moz-selection { background: #b9e9b9; color: #555; }
::-webkit-selection { background: #b9e9b9; color: #555; }

a { color: #6992c0; }
a:hover { color: #77a4dc; }

h2 { font-size: 2.9em; line-height: 1.4em; color: #626262; margin-bottom: 22px; }

p { font-size: 1.6em; line-height: 1.7em; color: #777; margin-bottom: 15px; }

#w {
  display: block;
  max-width: 900px;
  min-width: 300px;
  margin: 0 auto;
}

#content {
  margin: 0 1em;
  background: #fff;
  padding: 0 10px;
}

#pagebody {
  padding: 15px 25px;
}

My default CSS browser resets include a set of custom codes for the page typography. Also our body background tile is PS Neutral saved from Subtle Patterns. I had to split up the wrapper container and the body container into 2 divs because we are using side padding on the left and right to keep the page from growing to 100% width. Adding margin values onto the margin: 0 auto; property will result in losing the centered positioning.

/* navigation bar */
#topnav {
  display: block;
  width: 100%;
  position: relative;
}

#topnav #navbtn {
  display: none;
  float: right;
  top: 0;
  width: 20px;
  height: 70px;
  background: url('images/menu.png') center no-repeat;
  text-indent: -99999px;
  overflow: hidden;
}

#topnav nav {
  position: absolute;
  top: 0; 
  right: -10px;
}

#topnav nav ul {
  list-style: none;
}
#topnav nav ul li {
  display: block;
  float: left;
  font-size: 1.4em;
  margin-right: 4px;
}

#topnav nav ul li a {
  display: block;
  text-decoration: none;
  line-height: 70px;
  color: #8ea188;
  font-weight: bold;
  padding: 0 10px;
  border-bottom: 2px solid #fff;

}
#topnav nav ul li a:hover {
  color: #6f8767;
  background: #ddecd9;
  border-bottom-color: #bdd8b5;
}

By styling the inner <nav> element we retain control over the header bar. And by setting the position to absolute we control how far the nav links will follow after the title headline. Notice how the #navbtn will display hidden until resized lower than 560px using media queries. Everything else is setup including the height, width, and positioning.

Responsive Styles

There are just a small number of changes I have made when the browser window resizes smaller than 560px. I do not think this is a magical number, it just happens to be a nice area when the default navigation will start to bump up against the logo text. This is when we need to hide the nav menu and display the toggle icon.

/* responsive styles */
@media screen and (max-width: 560px) {
  h2 { font-size: 2.2em; }
  p { font-size: 1.45em; line-height: 1.55em; }
  #topnav { height: auto; }
  #topnav nav { 
    display: none; 
    position: static;
    width: 100%;
    top: auto;
    right: auto;
  }
  #topnav nav ul li { float: none; margin: 0; }
  #topnav nav ul li a {
    display: block;
    width: 100%;
    line-height: 1.4em;
    border: 0;
    padding: 6px 9px;
    background: #dcf4dc;
  }
  #topnav nav ul li a:hover {
    background: #cbdcc5;
  }
  #topnav nav ul li a.sel {
    color: #6f8767;
    background: #cbdcc5;
  }
  
  #topnav #navbtn {
    display: block;
  }
}

It also means removing the absolute positioning from the nav element so that it will render as a static block. The nav anchor links will display as full 100% width rows for quicker accessibility when tapping on a mobile screen. Also the inner page headers+paragraphs will be resized a bit smaller, coupled with adjusted line-height values.

Interactions with JavaScript

The final bit of code in menu.js could also be included directly into the HTML document. But since we need to handle more than a single toggle method, it is cleaner and more efficient to separate the markup from dynamic effects. I’ll break down this file into sections so that it is easier to read.

$(function(){
  var nb = $('#navbtn');
  var n = $('#topnav nav');
  
  $(window).on('resize', function(){
    

    if(nb.is(':hidden') && n.is(':hidden') && $(window).width() > 569) {
      // if the navigation menu and nav button are both hidden,
      // then the responsive nav is closed and the nav menu is still hidden.
      // just display the nav menu which will auto-hide at <560px width and remove class.
      $('#topnav nav').show().addClass('keep-nav-closed');
    }
  }); 

The first large chunk is arguably the most confusing. It deals with minor bugs when resizing browsers from responsive to non-responsive and shouldn’t affect smartphones at all. We attach an event handler to the window checking .on() resize. The two distinct logic statements are checking against the variable which target the header nav element, along with the nav menu toggle link.

When the browser goes responsive and the user toggles the menu open/closed it will append an inline style attribute. This will have precedence over anything written in the stylesheet and it is a problem after the menu has been opened & closed. The nav will have a permanent style of display: none; even after being resized larger than 560px.

This is what the 2nd if{} statement is checking for. When the nav and menu button are both hidden then the layout was responsive, the user opened/closed the menu, and then resized back to regular view. So we just need to display the nav again but there is a problem. jQuery’s show() method will also attach styles inline which means when resizing back to responsive the nav menu stays open permanently. To fix this I will be adding a class .keep-nav-closed. This will only get added if the nav is closed and then resized bigger – if it is left open when responsive it’ll stay open when resized larger and back down again.

    if($(this).width() < 570 && n.hasClass('keep-nav-closed')) {
      // if the nav menu and nav button are both visible,
      // then the responsive nav transitioned from open to non-responsive, then back again.
      // re-hide the nav menu and remove the hidden class
      $('#topnav nav').hide().removeAttr('class');
    }

Now the other if{} statement should make sense as to why we are checking for this class. It will only appear after the 2nd time of resizing and opening the nav, so we know to instead take off the class that way it stays closed when resizing back to a responsive width. It is a weird bug and I have yet to find a smaller or quicker solution for handling the inline jQuery styles, other than removing the attribute altogether(which still causes issues). So now let’s move on and take a look at the final segment of JavaScript.

  $('#topnav nav a,#topnav h1 a,#btmnav nav a').on('click', function(e){
    e.preventDefault(); // stop all hash(#) anchor links from loading
  });
  
  $('#navbtn').on('click', function(e){
    e.preventDefault();
    $("#topnav nav").slideToggle(350);
  });

Both event handlers are checking for click events on different targets. The first is locked onto all the different anchor links in the header navigation and footer sections. The href values are hash symbols (#) that do not go anywhere so I just set them to not load anything. This is similar when clicking the nav toggle because we do not want to load a hash into the page URL. Instead the responsive hidden nav menu will toggle open and closed at 350 milliseconds completely ignoring the href value.

mobile responsive sliding nav toggle with jquery

Live DemoDownload Source Code

Comments & Discussion

No discussion just yet... Get things started!

Leave a Comment

Subscribe
Membership
About the Author