Meta Q how to: Responsive toggle menus brought to you by META Q
Interior Ad 2 brought to you by The META Q

Meta Q how to: Responsive toggle menus

I was recently tasked with building a responsive navigation menu for a project I was on working at Q Digital Studio. It works thusly: when the browser window is at mobile sizes, the navigation bar "magically" transforms into a dropdown list. You can view a demo of it here.

The specs were pretty straightforward:

  • At mobile sizes, the navigation should display as a drop down menu.

  • When the user taps the dropdown, the links should slide open.

  • At tablet sizes and up, the navigation should display as a horizontal bar.

Here's how we can make the magic happen.

First let's build out our navigation.

<nav class="group">
  <h2 class="navheader slide-trigger">Menu <span></span></h2>
  <ul class="navigation group">
    <li><a href="#">Home</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Team</a></li>
    <li><a href="#">Portfolio</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

Now let's add some styling. Nothing too fancy just yet.

.navigation {
  max-width: none;
  background: #000;
  padding: 0;
}

.navigation li {
  float: left;
}

.navigation li a {
  display: block;
  color: #fff;
  padding: 10px;
}

.navigation li a:hover {
  background: #0fcaf2;
}

Next, we'll need to add a toggle button that will trigger the menu slide on tap. Let's hide it by default. We can show it at mobile sizes later.

Right above my navigation HTML  add:

<h2 class="navheader collapse-trigger">Menu <span></span></h2>


We'll also include a span tag to display an arrow icon to the button.

Now, let's add some styles for our toggle button:

.navheader { 
  font-size: 12px;
}

.slide-trigger {
  display: none; /* need this */
  border: 1px solid #CCCCCC;
  cursor: pointer;
  margin-left: 15px;
  margin-right: 15px;
}

.slide-trigger span {
  background-image: url("/a/i/dropdown-arrows.png");
  background-position: 0 -14px;
  display: block;
  float: right;
  margin-top: 3px;
  height: 14px;
  width: 32px;
}

It's time for the responsive magic.

We'll need to add some styles that hide the menu, but show the trigger button at screen sizes of 560 pixels or less (you can change that number to suit your needs). We'll also want to un-float the navigation links.

@media only screen and (max-width: 560px) {
  .slide-trigger { display: block; }
    .no-js .slide-trigger { display: none; }
  .navigation { display: none; }
    .no-js .navigation { display: block; }
  .navigation { margin: 0 15px; }
  .navigation li { float: none; }
  .navigation li a { border-bottom: 1px solid #fff; }
}

Notice that I've added some .no-js styles. Since we'll be using jQuery to handle the sliding behavior, we want to be sure the navigation is visible, in case JavaScript is turned off (we're using modernizr to test for JavaScript).

Here's the script that we'll use to handle the sliding behavior:

/* MOBILE COLLAPSE MENU */
(function($) {
  $.fn.collapsable = function(options) {
    // iterate and reformat each matched element
    return this.each(function() {
      // cache this:
      var obj = $(this);
      var tree = obj.next('.navigation');
      obj.click(function(){
        if( obj.is(':visible') ){tree.toggle();}
      });
      $(window).resize(function(){
        if ( $(window).width() <= 570 ){tree.attr('style','');};
      });
    });
  };
})(jQuery);

Now let's attach this little plug-in to our toggle button element. The script is pretty simple; it finds the next .navigation element and opens it or closes it when the toggle button is clicked. We also want to make sure the menu is visible again in case the window gets larger than 560 pixels, so let's add a little resize testing and open the navigation down at sizes of 560 pixels or more.

We can attach our plug-in to the toggle button when the document is ready:

$(document).ready(function(){
    $('.slide-trigger').collapsable();
});


There you have it! A working mobile toggle menu. Resize your window and watch the magic happen.

As always, feel free to tell us how you could have done this better! If you have questions, feel free to leave a comment below.


Terris Kremer's avatar

Terris Kremer

Front-end developer at Q Digital Studio

Terris Kremer is a front-end developer at Q Digital Studio. While he may joke that both front- and back-end developers are dweebs, Terris really does make development look cool. For Terris, work is a game that he can (and does) play all day long.

Posted

7.10.2012

Categories

Code > CSS > HTML > jQuery

Tags

20 comments >

What others are saying

Martijn

Thanks for this! It is very useful. I really appreciate it.

I found a little mistype in the line:

<h2 class=“navheader collapse-trigger”>Menu <span></span></h2>

I think ‘collapse-trigger’ should be ‘slide-trigger’.

 

Terris Kremer

@Martijn - Thanks reading and for finding my mistake. It’s now corrected in the article.

Leanda

This is great thanks! One tiny problem I’ve come across though that you might be able to help with.

The menu stays open after resizing back through the media query on smaller viewports. So if I start with a wide viewport and make the browser window really skinny, toggle open the menu and then toggle the menu closed. Make the browser window wide again and then back down to a skinny window the menu displays as always open until I refresh and start again.

Any help much appreciated.

Terris Kremer

@Leanda - Thanks for commenting! I see what you’re after - once the menu is opened in “mobile views” is stays open when resizing between breaking points.

The reason this happens is because the JavaScript we’ve written adds an inline style of display:block to the ul.navigation element on click. This inline style supersedes our media query styles and so they won’t apply the display:none at the “less than” breaking point.

One way to get around this is to change our script so that when the window is resized larger, past our breaking point, we remove that inline style attribute instead of using slideDown.

It looks like this:

$(window).resize(function(){
  if ( $(window).width() <= 570 ){tree.attr(‘style’,’‘);};
});

I’ve updated that in the demo and the article.

Thanks for pointing this out.

James Bowskill

Very useful - thanks for this. After implementing it locally I was surprised to find it not working on iPhone/iOS 5 though (neither my implementation, nor the demo page linked from here). The menu simply won’t open. Is it just me, or has anyone else found this?

Would really appreciate any help.

Terris Kremer

@James - You’re right. It turns out, unfortunately, that iOS5 doesn’t like slideDown() or slideUp(), so we’ll need to use .toggle(). We lose the slide animation, but atleast it will work this way :)

I’ve changed this in the jQuery example code so you can see what I mean.

James Bowskill

Thanks Terris. Shame the animation is lost, but better than no menu at all.

Kjetil Prestesæter

Thanks for a nice menu! One problem, though:

When I open the menu on my mobile, and scroll down to see the whole menu, it closes as soon as the H2-heading scrolls out of view. So it’s impossible to use the bottom items in a long menu. How to fix that?

Damon

Experiencing the same issue as Kjetil on some mobile devices. As soon as the screen is scrolled, the menu disappears…

kim

Thanks for nice menu. However, my lightbox js doesn’t work at all after i use this menu. Do you know how to solve this?

BP

Forgive my javascript illiteracy here. When you say “attach” the script to the toggle button ... what exactly do I do with this little bit of code?

$(document).ready(function(){
  $(’.slide-trigger’).collapsable();
});

daniel

This does not work with IE8 or less..
which is a pitty :-(

Any idea why?

Alex

today i’ve tried you responsive toggle menu, that’s cool and work pretty fine. But it has an inconvenient. After toggle and resize window, the old menu disapear. I find the solution line with

$(window).resize(function(){
  if ( $(window).width() <= 570 ){tree.attr(‘style’,’‘);};
});

replaced with

$(window).resize(function(){
  tree.removeAttr(‘style’);
});

For me it worked.

Deep

Hi Alex,

I’ve tried this but when we replace this

$(window).resize(function(){
  if ( $(window).width() <= 570 ){tree.attr(‘style’,’‘);};
});

with

$(window).resize(function(){
  tree.removeAttr(‘style’);
});

then It solved the old menu disappear problem but it makes the toggle stop woking. Any help regarding this will be highly appreciated.

jomurgel

I actually removed the tid-bit below entirely.

$(window).resize(function(){
      if ( $(window).height() <= 1270 ){tree.attr(‘style’,’‘);};
    });

Works fine for me now.  Still toggles, works on Android, and iOS.  Haven’t seen any issues.

Carlo Fardella

Just wanted to let you know that the script was failing for me when trying to integrate into a Wordpress theme and you need to change the script from:

$(document).ready(function(){
  $(’.slide-trigger’).collapsable();
});

to:

jQuery(document).ready(function ($) {
  $(’.slide-trigger’).collapsable();
});

This is due to Wordpress loading jQuery in conflicts mode. More info can be found the solution here:

http://stackoverflow.com/questions/10807200/jquery-uncaught-typeerror-property-of-object-object-window-is-not-a-funct

Cheers,
Carlo

Juan Pablo

I have tried using your html,css, and jquery. Do I need to attach any extra files because its not working for me. I get an error on the javascript. I use visual studio 2010.

Colleen Gratzer

Hello. I don’t know my way around javascript too well. Where do you place those last two “windows” of code above?

Jessica Seemann

thanks for this menu! but how can i use it for a drop down menu? i need a second level…?

Chris Ware

I have managed to get the menu working with a second level for those who are interested, site is http://parcelcourierservices.com/


Speak your mind