A tutorial on how to re-create the slide out sidebar menu that can be seen on the Google Nexus 7 website.
Today I want to show you how to reconstruct the sidebar menu of the
Google Nexus 7 page. It slides out with a really nice effect where some
subitems get expanded as well. When hovering over a special menu icon,
the sidebar icons will be revealed. When clicking on the icon, the whole
sidebar menu will be shown. The first sidebar menu item is a search
input which is styled the same way like the other menu items.
We will reconstruct this menu using unordered, nested lists and some CSS transitions. We’ll use JavaScript to apply classes for the opening effects and handling the hover and click events. With the help of a media query, we’ll adjust the size to make sense for smaller devices.
So let’s get started!
Our menu will consist of two main parts: a main menu, the one that
you can see at the top like a header, and the sidebar menu. We’ll give
the class “gn-menu-main” to the first one and wrap the second one in a
The first menu item will contain the menu icon anchor and the
Inside of the
Now, let’s style everything.
Let’s start by setting border-box for all the box-sizing:
Since we’ll be using an icon font for the icons, we’ll head over to IcoMoon and select some nice icons from Matthew Skiles’ Eco Ico set.
Later we’ll use a pseudo element to add the icons to the anchors.
But let’s style all the lists before:
These are some general (reset) styles for the lists and the sublists.
Now, let’s specify the styles for the main list. It will be fixed to the top of the page and we’ll give it a height of 60 pixels:
The general style for all the links in our menu and submenus will be the following:
Let’s also define some hover styles where we invert the colors. For
the first sidebar menu item which will have a search input, we’ll need a
special hover style. There we won’t have an anchor that fills all the
item, so let’s define the hover on the li and control what happens to
the icon (the anchor) and the li itself:
The list item childen will float left and they’ll have a right border:
The first list item will be the special trigger item and because we
will hide the text and use a pseudo element for the menu icon, we will
set the user-select to none and the width to be the same as the items’
height.
The last item in our main list will be floated right and we’ll swap the border:
The anchors for the main menu will have some padding and we’ll style the text a bit differently:
Let’s clear the floats with the following micro clearfix hack by Nicolas Gallagher:
Alright, so now it just misses the style of the menu icon, but let’s
leave that one for later when we define the other icon pseudo classes.
Let’s move on to the wrapper for the sidebar menu. Why do we need those extra wrappers? Well, if you don’t mind having a scrollbar visible you might as well get rid of them and simply set the menu to overflow-y: scroll. But since the scrollbar really breaks our minimal design in browsers on Windows, we’ll use a little trick to hide it. We’ll set the main wrapper to be overflow hidden, with a certain width (initially it’s just wide enough to see the icon bar). Then we’ll give the scroll wrapper a slightly larger width and a height of 100%. The scrollbar will be hidden. Our menu will then extend to the height it needs and it will be scrollable.
Initially we want to hide the menu, so we’ll give it a negative left value (of its width). Why are we not using 2D translate here?
Let’s add a box shadow for separating the list items. This will help us avoid double lines when hiding the submenu items:
Let’s add a transition the the submenu list items and set their initial height to 0:
The color will be slightly lighter than the parent menu items:
Now, let’s style the special search item and the search input
specifically. We want to make it really subtle like on the Google Nexus
page, so we’ll give it a transparent background colors and make the
placeholders look like a normal menu item:
Most browsers will hide the placeholder when we click on the input
which is much better for the user to understand that this is an input.
Chrome does not have that behavior so we’ll use a little trick to
emulate the same thing by setting the color of the placeholder to
transparent once the user clicks on the input and focuses it:
On hover we will change the color of the input text to white, just
like we do to the other anchors (this is the text the user types):
We will also do that for the placeholder text:
The search icon anchor will be a special one because it won’t have
the text visible next to it. The whole list item is a trick box. You
see, by setting the icon anchor to position absolute, we will let the
search input start at the very left of our list item. But remember, we
gave the input a large left padding which will make the text start only
after out search icon. When clicking on the search icon we will be
actually clicking on the input, focusing it.
Now, let’s style the ::before pseudo element for the icons. We’ll set them to
Let’s define the content for all the icons:
Normally, we want the text of the anchor to show next to the icon but
sometimes, we only want to show the icon. But we don’t just want an
empty anchor, the text should still be in the HTML. So we will wrap
those special cases into a span which we will simply hide by setting the
width and height to 0 and the overflow to hidden. Why not simply using
Let’s not forget about our little menu icon in the main menu. So, we
won’t use an icon from the icon font here, although you of course could.
Instead we’ll create it with a box shadow that will use alternating
colors (background and blue) to create the three lines. You could also
use a gradient here if you prefer.
On hover, we will invert the box shadow colors:
And when it’s selected (our side menu is open), we’ll make it more blue:
The last thing we need to do is to define our two classes for opening
the menu for showing the icons only and for showing the whole menu.
When we hover over the menu icon, we will show the icons only. Let’s
call this class gn-open-part. The other class, gn-open-all
will be applied either if we click on the main menu icon or if we hover
over the icon part shown (the menu sidebar menu itself).
In both cases, we’ll need to reset the translate to 0:
If we want to open the whole menu, we’ll need to set the right width:
Opening the whole menu, should also expand the submenu items:
Last, but not least, our precious media query that will make the menu use the whole width of the screen:
And we’ll also adjust the width of the scroll wrapper to be larger
than the 100%. This is probably not too important as we don’t see
scrollbars on most devices of that size.
Alright, now that we’ve styled everything, we’ll use some JavaScript for the logic of opening and closing the menu (i.e. applying the classes).
We start by caching some elements and initializing some variables. The bodyClickFn function defines what happens when the menu is open and we click somewhere else on the document. We should also take care of touch events.
Let’s take a look at the events that need to be initialized.
We want to open the first part of the menu (let’s call it icon menu) when the main menu icon (trigger) is hovered. When we move the mouse out this same menu should slide back in.
Once the icon menu is in the viewport, hovering over it will make the
rest of the menu slide out. After it slides out, and we click somewhere
on the body then the menu should slide back in. We need to bind the
respective event (click or touchstart) to the document.
Finally if we click the menu icon, we want the whole menu to slide
out or slide in if it’s already in the viewport. We will also bind (or
unbind) the respective event (click or touchstart) to the document.
One last thing: we don’t want the menu to slide back in if we click
somewhere inside the menu area. Since we are binding the
click/touchstart event to the document (so that the menu closes) we need
to do the following:
And here is the final _initEvents function and the methods to open and close the menu.
And that’s all! Thank you for reading and I hope you enjoyed this tutorial and find it useful!
Find this project on Github
We will reconstruct this menu using unordered, nested lists and some CSS transitions. We’ll use JavaScript to apply classes for the opening effects and handling the hover and click events. With the help of a media query, we’ll adjust the size to make sense for smaller devices.
So let’s get started!
The Markup
Our menu will consist of two main parts: a main menu, the one that
you can see at the top like a header, and the sidebar menu. We’ll give
the class “gn-menu-main” to the first one and wrap the second one in a nav
element. You could of course use any structure that you prefer.The first menu item will contain the menu icon anchor and the
nav
element:
1
2
3
4
5
6
7
8
9
10
11
| < ul id = "gn-menu" class = "gn-menu-main" > < li class = "gn-trigger" > < a class = "gn-icon gn-icon-menu" >< span >Menu</ span ></ a > < nav class = "gn-menu-wrapper" > <!-- ... --> </ nav > </ li > < li >< a href = "http://tympanus.net/codrops" >Codrops</ a ></ li > < li > <!-- ... --> </ li > <!-- ... --> </ ul > |
nav
element we’ll add another wrapper that
will help us with hiding the nasty scrollbar for Windows browsers. The
heart of this submenu is the unordered list with the class “gn-menu”. It
will consist of list items, some of which will have a sublist. The
first item will be the special search input:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| < div class = "gn-scroller" > < ul class = "gn-menu" > < li class = "gn-search-item" > < input placeholder = "Search" type = "search" class = "gn-search" > < a class = "gn-icon gn-icon-search" >< span >Search</ span ></ a > </ li > < li > < a class = "gn-icon gn-icon-download" >Downloads</ a > < ul class = "gn-submenu" > < li >< a class = "gn-icon gn-icon-illustrator" >Vector Illustrations</ a ></ li > < li >< a class = "gn-icon gn-icon-photoshop" >Photoshop files</ a ></ li > </ ul > </ li > < li >< a class = "gn-icon gn-icon-cog" >Settings</ a ></ li > < li > <!-- ... --> </ li > <!-- ... --> </ ul > </ div > <!-- /gn-scroller --> |
The CSS
Note that the CSS will not contain any vendor prefixes, but you will find them in the files.Let’s start by setting border-box for all the box-sizing:
1
2
3
4
5
| *, *:after, *::before { box-sizing : border-box; } |
1
2
3
4
5
6
7
| @font-face { font-weight : normal ; font-style : normal ; font-family : 'ecoicons' ; src : url ( "../fonts/ecoicons/ecoicons.eot" ); src : url ( "../fonts/ecoicons/ecoicons.eot?#iefix" ) format ( "embedded-opentype" ), url ( "../fonts/ecoicons/ecoicons.woff" ) format ( "woff" ), url ( "../fonts/ecoicons/ecoicons.ttf" ) format ( "truetype" ), url ( "../fonts/ecoicons/ecoicons.svg#ecoicons" ) format ( "svg" ); } |
But let’s style all the lists before:
1
2
3
4
5
6
7
8
9
10
11
12
| .gn-menu-main, .gn-menu-main ul { margin : 0 ; padding : 0 ; background : white ; color : #5f6f81 ; list-style : none ; text-transform : none ; font-weight : 300 ; font-family : 'Lato' , Arial , sans-serif ; line-height : 60px ; } |
Now, let’s specify the styles for the main list. It will be fixed to the top of the page and we’ll give it a height of 60 pixels:
1
2
3
4
5
6
7
8
| .gn-menu-main { position : fixed ; top : 0 ; left : 0 ; width : 100% ; height : 60px ; font-size : 13px ; } |
1
2
3
4
5
6
7
| .gn-menu-main a { display : block ; height : 100% ; color : #5f6f81 ; text-decoration : none ; cursor : pointer ; } |
1
2
3
4
5
6
| .no-touch .gn-menu-main a:hover, .no-touch .gn-menu li.gn-search-item:hover, .no-touch .gn-menu li.gn-search-item:hover a { background : #5f6f81 ; color : white ; } |
1
2
3
4
5
6
7
| .gn-menu-main > li { display : block ; float : left ; height : 100% ; border-right : 1px solid #c6d0da ; text-align : center ; } |
1
2
3
4
5
| .gn-menu-main li.gn-trigger { position : relative ; width : 60px ; user-select : none ; } |
1
2
3
4
5
| .gn-menu-main > li:last-child { float : right ; border-right : none ; border-left : 1px solid #c6d0da ; } |
1
2
3
4
5
6
| .gn-menu-main > li > a { padding : 0 30px ; text-transform : uppercase ; letter-spacing : 1px ; font-weight : bold ; } |
1
2
3
4
5
| .gn-menu-main:after { display : table; clear : both ; content : '' ; } |
Let’s move on to the wrapper for the sidebar menu. Why do we need those extra wrappers? Well, if you don’t mind having a scrollbar visible you might as well get rid of them and simply set the menu to overflow-y: scroll. But since the scrollbar really breaks our minimal design in browsers on Windows, we’ll use a little trick to hide it. We’ll set the main wrapper to be overflow hidden, with a certain width (initially it’s just wide enough to see the icon bar). Then we’ll give the scroll wrapper a slightly larger width and a height of 100%. The scrollbar will be hidden. Our menu will then extend to the height it needs and it will be scrollable.
Initially we want to hide the menu, so we’ll give it a negative left value (of its width). Why are we not using 2D translate here?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| .gn-menu-wrapper { position : fixed ; top : 60px ; bottom : 0 ; left : 0 ; overflow : hidden ; width : 60px ; /* will be transitioned to 340px */ border-top : 1px solid #c6d0da ; background : white ; transform : translateX ( -60px ); /* will be transitioned to 0px */ transition : transform 0.3 s, width 0.3 s; } .gn-scroller { position : absolute ; overflow-y: scroll ; width : 370px ; height : 100% ; } .gn-menu { border-bottom : 1px solid #c6d0da ; text-align : left ; font-size : 18px ; } |
1
2
3
4
| .gn-menu li:not(:first-child), .gn-menu li li { box-shadow : inset 0 1px #c6d0da } |
1
2
3
4
5
| .gn-submenu li { overflow : hidden ; height : 0 ; transition : height 0.3 s; } |
1
2
3
| .gn-submenu li a { color : #c1c9d1 } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| input.gn-search { position : relative ; z-index : 10 ; padding-left : 60px ; outline : none ; border : none ; background : transparent ; color : #5f6f81 ; font-weight : 300 ; font-family : 'Lato' , Arial , sans-serif ; cursor : pointer ; } /* placeholder */ .gn-search::-webkit-input-placeholder { color : #5f6f81 } .gn-search:-moz-placeholder { color : #5f6f81 } .gn-search::-moz-placeholder { color : #5f6f81 } .gn-search:-ms-input-placeholder { color : #5f6f81 } |
1
2
3
4
5
6
7
8
| .gn-search:focus::-webkit-input-placeholder, .no-touch .gn-menu li.gn-search-item:hover .gn-search:focus::-webkit-input-placeholder { color : transparent } input.gn-search:focus { cursor : text } |
1
2
3
| .no-touch .gn-menu li.gn-search-item:hover input.gn-search { color : white } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| /* placeholder */ .no-touch .gn-menu li.gn-search-item:hover .gn-search::-webkit-input-placeholder { color : white } .no-touch .gn-menu li.gn-search-item:hover .gn-search:-moz-placeholder { color : white } .no-touch .gn-menu li.gn-search-item:hover .gn-search::-moz-placeholder { color : white } .no-touch .gn-menu li.gn-search-item:hover .gn-search:-ms-input-placeholder { color : white } |
1
2
3
4
5
6
| .gn-menu-main a.gn-icon-search { position : absolute ; top : 0 ; left : 0 ; height : 60px ; } |
inline-block
and give them a width of 60 pixel. We have to reset all the font
styles, because now we’ll be using our icon font that we’ve included in
the beginning of the CSS:
1
2
3
4
5
6
7
8
9
10
11
12
13
| .gn-icon::before { display : inline-block ; width : 60px ; text-align : center ; text-transform : none ; font-weight : normal ; font-style : normal ; font-variant : normal ; font-family : 'ecoicons' ; line-height : 1 ; speak : none ; -webkit- font-smoothing : antialiased ; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| .gn-icon-help::before { content : "\e000" } .gn-icon-cog::before { content : "\e006" } .gn-icon-search::before { content : "\e005" } .gn-icon-download::before { content : "\e007" } .gn-icon-photoshop::before { content : "\e001" } .gn-icon-illustrator::before { content : "\e002" } .gn-icon-archive::before { content : "\e00d" } .gn-icon-article::before { content : "\e003" } .gn-icon-pictures::before { content : "\e008" } .gn-icon-videos::before { content : "\e009" } |
display: none
? Hiding the content like that would make it inaccessible to screen readers, so let’s make sure that we don’t “erase” anything so important for them:
1
2
3
4
5
6
| .gn- icon span { width : 0 ; height : 0 ; display : block ; overflow : hidden ; } |
1
2
3
4
5
6
7
8
9
| .gn-icon-menu::before { margin-left : -15px ; vertical-align : -2px ; width : 30px ; height : 3px ; background : #5f6f81 ; box-shadow : 0 3px white , 0 -6px #5f6f81 , 0 -9px white , 0 -12px #5f6f81 ; content : '' ; } |
1
2
3
4
5
| .no-touch .gn-icon-menu:hover::before, .no-touch .gn-icon-menu.gn-selected:hover::before { background : white ; box-shadow : 0 3px #5f6f81 , 0 -6px white , 0 -9px #5f6f81 , 0 -12px white ; } |
1
2
3
4
| .gn-icon-menu.gn-selected::before { background : #5993cd ; box-shadow : 0 3px white , 0 -6px #5993cd , 0 -9px white , 0 -12px #5993cd ; } |
In both cases, we’ll need to reset the translate to 0:
1
2
3
4
| .gn-menu-wrapper.gn-open- all , .gn-menu-wrapper.gn-open-part { transform : translateX ( 0px ); } |
1
2
3
| .gn-menu-wrapper.gn-open- all { width : 340px ; } |
1
2
3
| .gn-menu-wrapper.gn-open- all .gn-submenu li { height : 60px ; } |
1
2
3
4
5
6
7
8
9
10
| @media screen and ( max-width : 422px ) { .gn-menu-wrapper.gn-open- all { transform : translateX ( 0px ); width : 100% ; } .gn-menu-wrapper.gn-open- all .gn-scroller { width : 130% ; } } |
Alright, now that we’ve styled everything, we’ll use some JavaScript for the logic of opening and closing the menu (i.e. applying the classes).
The JavaScript
So let’s create a small script that will take care of the menu functionality. When we hover over the menu icon, we want the first part of the menu to slide out so that we can see the icons. If we hover over the sidebar menu area or if we click on the main menu icon, then the rest of the menu should slide out. Clicking the menu icon again or clicking on any other part of the body should make the whole menu slide back in. So let’s see how we can pull all that off.We start by caching some elements and initializing some variables. The bodyClickFn function defines what happens when the menu is open and we click somewhere else on the document. We should also take care of touch events.
1
2
3
4
5
6
7
8
9
10
11
12
13
| _init : function () { this .trigger = this .el.querySelector( 'a.gn-icon-menu' ); this .menu = this .el.querySelector( 'nav.gn-menu-wrapper' ); this .isMenuOpen = false ; this .eventtype = mobilecheck() ? 'touchstart' : 'click' ; this ._initEvents(); var self = this ; this .bodyClickFn = function () { self._closeMenu(); this .removeEventListener( self.eventtype, self.bodyClickFn ); }; } |
We want to open the first part of the menu (let’s call it icon menu) when the main menu icon (trigger) is hovered. When we move the mouse out this same menu should slide back in.
1
2
| this .trigger.addEventListener( 'mouseover' , function (ev) { self._openIconMenu(); } ); this .trigger.addEventListener( 'mouseout' , function (ev) { self._closeIconMenu(); } ); |
1
2
3
4
| this .menu.addEventListener( 'mouseover' , function (ev) { self._openMenu(); document.addEventListener( self.eventtype, self.bodyClickFn ); } ); |
1
2
3
4
5
6
7
8
9
10
11
12
| this .trigger.addEventListener( this .eventtype, function ( ev ) { ev.stopPropagation(); ev.preventDefault(); if ( self.isMenuOpen ) { self._closeMenu(); document.removeEventListener( self.eventtype, self.bodyClickFn ); } else { self._openMenu(); document.addEventListener( self.eventtype, self.bodyClickFn ); } } ); |
1
| this .menu.addEventListener( this .eventtype, function (ev) { ev.stopPropagation(); } ); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| _initEvents : function () { var self = this ; if ( !mobilecheck() ) { this .trigger.addEventListener( 'mouseover' , function (ev) { self._openIconMenu(); } ); this .trigger.addEventListener( 'mouseout' , function (ev) { self._closeIconMenu(); } ); this .menu.addEventListener( 'mouseover' , function (ev) { self._openMenu(); document.addEventListener( self.eventtype, self.bodyClickFn ); } ); } this .trigger.addEventListener( this .eventtype, function ( ev ) { ev.stopPropagation(); ev.preventDefault(); if ( self.isMenuOpen ) { self._closeMenu(); document.removeEventListener( self.eventtype, self.bodyClickFn ); } else { self._openMenu(); document.addEventListener( self.eventtype, self.bodyClickFn ); } } ); this .menu.addEventListener( this .eventtype, function (ev) { ev.stopPropagation(); } ); }, _openIconMenu : function () { classie.add( this .menu, 'gn-open-part' ); }, _closeIconMenu : function () { classie.remove( this .menu, 'gn-open-part' ); }, _openMenu : function () { if ( this .isMenuOpen ) return ; classie.add( this .trigger, 'gn-selected' ); this .isMenuOpen = true ; classie.add( this .menu, 'gn-open-all' ); this ._closeIconMenu(); }, _closeMenu : function () { if ( ! this .isMenuOpen ) return ; classie.remove( this .trigger, 'gn-selected' ); this .isMenuOpen = false ; classie.remove( this .menu, 'gn-open-all' ); this ._closeIconMenu(); } |
Find this project on Github
0 komentar:
Post a Comment