Tutorial - Unobtrusive Mobile Navigation
Remember when Facebook for iOS had a dashboard?
Remember tapping ‘News feed’ as soon as it appeared?
When Facebook redesigned the app, they reassessed the typical user’s journey and realised that, for the most part, in the absence of any exciting red notifications it’s safe to assume you probably just want to scroll your News Feed in search of cats, babies & food.
Recently I’ve been developing a responsive website for a popular lifestyle magazine in the Middle East. The site’s focus is to draw users into magazine articles by presenting them on an attractive responsive mosaic on the home page. This lends itself well to mobile & the navigation is secondary. The site had already been designed; the navigation was to appear over the top of the website until the user chose an option. I coded it up then wondered how a user could opt out of selecting a nav item and return to the home page underneath. They couldn’t.
I could have built a button at the top of the menu to close it, or I could have opted for a less obtrusive solution. I’d been doing some reading on the subject and came across Brad Frost’s ‘Responsive Design Patterns’ chapter in Smashing Magazine’s new publication: The Mobile Book. Brad compares and contrasts the different mobile navigation options available to developers. Read more on Brad’s blog.
What are the alternatives? The off-canvas pattern is a clever solution that moves a page component just off screen and, when requested, exposes the content using a sliding-door effect. This technique was popularized by Facebook’s mobile fly-out navigation. The off-canvas approach reserves the visible screen area for core content, while keeping additional content off to the side for easy access.
Fantastic, that’s perfect. I had a quick chat with our designer & he agreed I should have a quick play around with an alternative navigation.
What are we making?
We’re going to create a HTML, CSS & JS version of the fly-out menu from the iOS apps developed by Facebook, Path & Spotify (amongst others) for use on mobile websites. If you’re unfamiliar with this type of fly-out menu I suggest you download one of those apps & take a look.
A user can tap one of the navigation buttons in the header, which slides the body of the website to one side, revealing a navigation menu underneath. This grabs the user’s attention but still provides an obvious way of returning to the main body of the site if they wish.
Note: The green and purple buttons are clickable in this example, but in your mobile version you won’t need clicking enabled as your users will be on a touch device such as a smartphone or tablet.
We’re using Hammer.js for the touch events.
Use the live example on the right, or open in a new tab.
How do I make it?
We need a main <div>
holding all of the main scrollable content of the website. I’ve given this an id of #body. <div#body>
will be sliding side-to-side to reveal the navigation menus underneath.
Inside <div#body>
we’ll have a <header>
holding two buttons. A button on the left will open the left menu, and the button on the right will open the right menu, simple. We’ll also have a <div>
to hold the main page content.
We create two navigation menus, using the <nav>
tag. These will be fixed to the top left & top right of the web page and will sit underneath (using the z-index CSS attribute) <div#body>
until revealed.
Finally, we’ll need an overlay div, which will be invisible but perform the vital role of dealing with touch events over <div#body>
whilst the menus are open.
To recap, our HTML will look something similar to this:
<nav class="mobile-nav mobile-left-nav">
<ul>
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
<li><a href="#">Link 3</a></li>
</ul>
</nav>
<nav class="mobile-nav mobile-right-nav">
<ul>
<li><a href="#">Link 4</a></li>
<li><a href="#">Link 5</a></li>
<li><a href="#">Link 6</a></li>
<li><a href="#">Link 7</a></li>
</ul>
</nav>
<div id="body">
<header>
<a href="#" class="mobile-nav-button mobile-left-button">Toggle Left Navigation</a>
<a href="#" class="mobile-nav-button mobile-right-button">Toggle Right Navigation</a>
</header>
<div class="site-container">
Main page content here
</div>
</div>
<div class="overlay" style="display: none;"></div>
At this point it’s worth mentioning that your website should be using the device’s width appropriately. For this kind of user interaction it’s vital the user isn’t viewing a zoomed out version of the website.
Ensure the following meta tag is in the <head>
of your web page:
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0; user-scalable=no">
As I’ve stated, this tutorial is for intermediate developers so I’m just going to plough through the attributes as they’re fairly self-explanatory.
<div#body>
should utilise the full width of the viewport of your user’s device. We’d like to position the element relative to the other elements. I’ve used a grey (#CCC) background, a z-index of 2 (the navigation elements will have lower values to ensure they’re underneath). To enable native style scrolling on iOS we use -webkit-overflow-scrolling: touch; and we apply transition attributes to deal with the CSS3 animation applied when the margin-left attribute is altered (which we’ll be using to reveal the menus).
div#body {
width: 100%;
position: relative;
background: #CCC;
z-index: 2;
float: left;
-webkit-overflow-scrolling: touch;
transition: margin-left 0.2s;
-moz-transition: margin-left 0.2s; /* Firefox */
-webkit-transition: margin-left 0.2s; /* Safari and Chrome */
-o-transition: margin-left 0.2s; /* Opera */
}
Each of the navigation menus will have its own class for its own unique attributes, but they will also share a class with common attributes, which I’ve named .mobile-nav. Note the z-index of 0 is less than the z-index of 2 of <div#body>
. When we open a navigation menu we’ll change its z-index to 1 (still lower than <div#body>
) to ensure it overlaps the menu on the other side. This is only necessary for smaller viewports, but necessary all the same.
.mobile-nav {
width: 262px;
height: 100%;
background: white;
z-index: 0;
position: fixed;
top: 0;
z-index: 1;
border-top: 4px solid #55ACDA;
transition: left 0.2s;
-moz-transition: left 0.2s; /* Firefox 4 */
-webkit-transition: left 0.2s; /* Safari and Chrome */
-o-transition: left 0.2s; /* Opera */
transition: right 0.2s;
-moz-transition: right 0.2s; /* Firefox 4 */
-webkit-transition: right 0.2s; /* Safari and Chrome */
-o-transition: right 0.2s; /* Opera */
}
Not much to say here, each <nav>
menu has its own positioning value to fix to the left or the right of the page.
.mobile-left-nav {
left: 0;
}
.mobile-right-nav {
right: 0;
}
As mentioned earlier, we’ll also need an overlay <div>
to cover the webpage (except the menu currently open). This will allow us to attach touch events to help the user hide the menu again. There’s a nice trick to ensure the div covers the whole canvas using position: fixed;
. By setting the top, right, bottom and left values to zero it stretches the canvas. When we open a menu, we’ll alter the left or the right value to the width of the menu to ensure it’s offset that distance, and not covering the menu. Notice it has a z-index higher than the <nav>
menus, and <div#body>
.
.overlay {
width: auto;
height: auto;
display: block;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 3;
}
I’ve omitted the rest of the CSS from this tutorial as it’s all simply presentational CSS from here on in; yours will likely differ from mine. Feel free to use Web Inspector / Firebug to steal mine, though!
The JavaScript
First of all we set some variables so we don’t have to write them out each time we need them:
var nav_slide_px = 262; // Width of each menu
var div_body = $('div#body'); // The main body element
var left_nav = $('.mobile-left-nav'); // Left nav menu
var right_nav = $('.mobile-right-nav'); // Right nav menu
As we have some fairly general behaviours which can be defined, we’ll set them out in a number of functions. They’re fairly self-explanatory, but they’ll show / hide the left menu, show / hide the right menu. Each works by applying a margin-left (note it’s marginLeft in the .css() function in jQuery) value to <div#body>
to move it one way or another. We then offset <div.overlay>
by the width of the menu we’re revealing.
var show_left_nav = function() { div_body.css({'marginLeft' : '262px'}); left_nav.css({'zIndex' : '1'}); $('.overlay').css({'left' : nav_slide_px, 'right' : 0}).show(); };
var hide_left_nav = function() { div_body.css({'marginLeft' : '0'}); left_nav.css({'zIndex' : '0'}); $('.overlay').hide().css({'left' : 0, 'right' : 0}); };
var show_right_nav = function() { div_body.css({'marginLeft' : '-262px'}); right_nav.css({'zIndex' : '1'}); $('.overlay').css({'left' : '0', 'right' : nav_slide_px}).show(); };
var hide_right_nav = function() { div_body.css({'marginLeft' : '0'}); right_nav.css({'zIndex' : '0'}); $('.overlay').hide().css({'left' : 0, 'right' : 0}); };
We now want to be able to call these functions when certain events (such as a user touching / swiping the screen) occur.
/** * We want to bind a non-action to a touch / gesture anywhere on the * navigation menus except the links, so we call the .hammer() * function, but without binding any specific actions. We pass the * prevent_default option in a JavaScript object to the * .hammer() function to prevent any regular action (such as * horizontal scrolling) taking place. */ $('.mobile-nav').hammer({ prevent_default: true });
/** * Due to the line above, touch events aren't bound to the link * <a> elements within the menu, so we bind these separately. * We want them to simply open the URL provided in the href attribute. */ $('.mobile-nav a').hammer({ prevent_default: false }).bind('touchend', function() { window.location.href = $(this).attr('href'); });
We bind the ‘touchstart’, and ‘swipe’ events to the <a>
buttons in the <header>
. The ‘touchstart’ event fires as soon as you touch the button (i.e. before you release your finger), and ‘swipe’ allows for users swiping the button, which will be much less common. Inside each callback function we have an if statement to check whether the <div#body>
has already been shifted left or right, or whether it’s at its default position. We then know whether we’re opening or closing the menu, and can call the appropriate function.
// Deal with profile button touches / swipes $('.mobile-left-button').hammer({ prevent_default: true }).bind('touchstart swipe', function(ev){ if (parseInt(div_body.css('marginLeft'), 10) == nav_slide_px) { hide_left_nav(); } else { show_left_nav(); }
return; });
// Deal with nav button touches / swipes $('.mobile-right-button').hammer({ prevent_default: true }).bind('touchstart swipe', function(ev){ if (parseInt(div_body.css('marginLeft'), 10) == '-' + nav_slide_px) { hide_right_nav(); } else { show_right_nav(); }
return; });
Finally, once the appropriate show or hide function has been called, our overlay will now be present (yet invisible to the user). We need to ensure the touch events are bound on this overlay. On the user touching / gesturing on the overlay we immediately want to restore the <div#body>
to its default position so we bind ‘touchstart’, ‘dragstart’ and ‘swipe’ to ignore any default action and to hide the currently revealed menu.
Note: I played around with attaching touch events to <div#body>
but I couldn’t prevent the user from interacting with individual elements within the body, so I thought an overlay would be a simple fix for that. If anyone has any better way around this please submit a pull request!
$('.overlay').hammer().bind('touchstart dragstart swipe', function(ev){ // Ignore any default action ev.preventDefault(); ev.stopPropagation();
// Hide the currently revealed menu if (parseInt(div_body.css('marginLeft'), 10) == nav_slide_px) { hide_left_nav(); } else if (parseInt(div_body.css('marginLeft'), 10) == '-' + nav_slide_px) { hide_right_nav(); } return; });
Download the source on GitHub.