Recently I stumbled upon Codrops' tutorial regarding the recreation of Google Nexus navigation menu. That was inspiring and such a creative navigation system from Google. So, I decided to create a jQuery version to challenge to myself.
I have been playing with the original Google Nexus Menu for a while. Based on my observation, I summarised its behaviour as below:
- When hovering over the menu button, it will show icon only menu. And when you click on it, it will show full menu.
- When it's showing icon only menu, the menu will hide itself on mouse out. Whereas when it's showing full menu, it won't hide itself until you click on the web page.
- Icon only menu won't display secondary menu. Secondary menu will expand in full menu.
- It uses mobile touchstart event and also responsive.
Now we understand its characteristics, and it will be much more easier to code it. It's not 100% imitation but it captured the essence of Google Nexus menu. Below is the screenshot of the final result:
One thing though, since it's coded with CSS3, as a result, it only works in browsers that support CSS3 transition.A pure Javascript version is possible, but I have decided to embrace the new technology :)
HTML
As you can see, we will have to build two menus, and add interactions and animation transitions on them. The structure of the menu is really straight forward and standard. We are using UL
list for both.
Main Nav
<nav> <ul> <li><a href="#" class="icon icon-menu" id="btn-menu">Menu</a></li> </ul> </nav>
Side Nav
<div id="sideNav"> <ul> <li class="searchForm"> <a href="#" class="icon icon-search"> <span> <input type="text" placeholder="Search" class="search" /> </span> </a> </li> <li><a href="#" class="icon icon-home"><span>Parent</span></a> <ul> <li><a href="#"><span>Children</span></a></li> </ul> </li> </ul> </div>
CSS
We use fair a bit of CSS3 feature here such as transition, box-sizing and responsive layout. We will be using CSS3 transition to animate everything to take the advantage of GPU. I just reckon CSS3 animation is smoother than Javascript.
We put everything that need CSS transition together.
#sideNav, #sideNav.showHalfMenu, #sideNav.showFullMenu, #sideNav ul ul li, #sideNav.showFullMenu ul ul li { -webkit-transition: 0.2s ease; -moz-transition: 0.2s ease; -ms-transition: 0.2s ease; transition: 0.2s ease; }
And the rest of the styling for the menu.
html.cursor { cursor: pointer; } nav { font-family: 'Roboto', sans-serif; width: 100%; height: 59px; border-bottom:1px solid #ddd; position: fixed; top:0; left:0; z-index:20; background-color:#ffffff; } nav ul, #sideNav ul, #sideNav ul ul { margin:0; padding:0; list-style:none; } nav li { margin:0; float:left; border-right:1px solid #ddd; font-size:18px; } nav a, #sideNav a { color:#5b6064; text-decoration:none; display:block; padding:10px 30px; height:59px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; line-height:35px; } nav a:hover, #sideNav a:hover { color:#ffffff; background-color: #5b6064; } #sideNav { position: fixed; left:-60px; top:59px; width: 60px; height:100%; border-right:1px solid #ddd; background-color:#ffffff; overflow-y: auto; } #sideNav.showHalfMenu { left:0; } #sideNav.showFullMenu { left:0; width: 311px; } #sideNav.showFullMenu ul ul li { height:59px; } #sideNav > ul { width: 100%; padding-bottom:60px; } #sideNav ul li { width: 100%; margin:0; font-weight:300; } #sideNav ul li a { border-bottom:1px solid #ddd; padding-left:70px; } #sideNav ul li span { position: relative; top:3px; } #sideNav ul ul li { overflow:hidden; height: 0; }
Search
One of the features of the menu is, the first menu item of the side nav has a search field that blended into the menu. Not sure if it's a good practice but it does look sleek. This is where I picked up another new thing, we actually allow to change placeholder color!
#sideNav input.search { font-family: 'Roboto', sans-serif; border:0; outline:0; font-weight:300; background:transparent; color:#5b6064; } input.search::-webkit-input-placeholder { color:#5b6064; } input.search:-moz-placeholder { color:#5b6064; } input.search::-moz-placeholder { color:#5b6064; } input.search:-ms-input-placeholder { color:#5b6064; } #sideNav li.searchForm:hover input.search:focus, #sideNav li.searchForm:hover input.search::-webkit-input-placeholder { color:#fff; }
Responsive
Not much here, we just want the menu fit well on mobile. It will look like this:
@media only screen and (min-width: 200px) and (max-width: 480px) { nav a, #sideNav a { padding:10px 15px; } nav a#btn-menu { padding:10px 30px; } #sideNav.showFullMenu, #sideNav.showFullMenu li, #sideNav.showFullMenu a { width: 100%; } }
Javascript
And... the last section. Animation is just a matter of applying the right CSS classes at the right spot. We created a few functions such as showHalfMenu()
, showFullMenu()
, hideMenu()
and toggleMenu
so we can reuse them.
We also put in a mobile detection script I found from stackoverflow to make sure touch screen devices don't have to load desktop mouse events.
As usual, I added inline comments in the script to show how it's done.
$(function () { var GNmenu = { isMenuOpened: false, init : function () { var menuBtn = $('#btn-menu'); // make it work on touch screen device and mouse click menuBtn.on('touchstart click', function (e) { e.stopPropagation(); e.preventDefault(); /* if the menu is half show, show the whole menu instead of hide it */ if ($('#sideNav').hasClass('showHalfMenu') && !$('#sideNav').hasClass('showFullMenu')) { GNmenu.showFullMenu(); } else { GNmenu.toggleMenu(); } }); // only do all this if it's desktop if (!GNmenu.isMobile()) { menuBtn.bind('mouseover', function () { GNmenu.showHalfMenu(); }); menuBtn.bind('mouseout', function () { GNmenu.hideMenu(); }); $('#sideNav').bind('mouseover', function () { GNmenu.showFullMenu(); }); // this allow user to hide the menu by clicking on web page body GNmenu.bodyClick(); } // search form // unbind the bodyClick event $('.searchForm input[type=text]').focus(function () { $('html').unbind('click'); }).blur(function() { GNmenu.bodyClick(); }); }, bodyClick: function () { $('html').bind('click',function () { if (GNmenu.isMenuOpened) { GNmenu.hideMenu(); } }); }, toggleMenu: function () { if (!GNmenu.isMenuOpened) { GNmenu.showFullMenu(); } else { GNmenu.hideMenu(); } }, showHalfMenu: function () { $('#sideNav').addClass('showHalfMenu'); GNmenu.isMenuOpened = true; }, showFullMenu: function () { $('#btn-menu').addClass('icon-menu-active'); $('#sideNav').addClass('showFullMenu'); $('html').addClass('cursor'); GNmenu.isMenuOpened = true; }, hideMenu: function () { $('#btn-menu').removeClass('icon-menu-active'); $('#sideNav').removeClass('showFullMenu showHalfMenu'); $('html').removeClass('cursor'); GNmenu.isMenuOpened = false; }, // Mobile Detection: http://stackoverflow.com/a/11381730 isMobile: function() { var check = false; (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera); return check; } } // Run the Google Nexus Inspired Menu GNmenu.init(); });
Conclusion
It's not hard to replicate something from sketch. What we need to do is to understand its behavior. Also, get your web developer tools ready, you will need to do a lot of code inspections for better understanding of how it works.
We just recreated a replica of Google Nexus menu with jQuery library. If you preferred something standalone, read tutorial from Codrops by Mary Lou, she did it without jQuery library.
Drop us a comment if you have any questions and stay tuned with us for more posts like this.
Comments will be moderated and
rel="nofollow"
will be added to all links. You can wrap your coding with[code][/code]
to make use of built-in syntax highlighter.One modification, we can use font-awesome icons instead.
When I tested on my phone (Galaxy S4) noticed that clicking outside the menu it would not close. I then tested with your demo and the same problem occurred.
Do you know what could be the problem?
Thanks in advance.
I have a question. Is possible that the submenu (in your example is web design, web developer) only appears when the user click or hover in the menu item that contains the subcategory?
Thank you very much!