Learn and share. The simplest harmony.

 

How to Create a Walking Navigation

Posted on February 16, 2012 and got 6 shouts so far

On a single page website with fixed position navigation, it will be nice to tell user on what section they are reading at. In this tutorial I am going to share how to create a walking navigation, on the other words, auto focus navigation based on user scrolling, by taking advantage of both jQuery and CSS animation.

The Trick

We can estimate which section the users are reading at by checking their current scroll position and get closest section from it. This mean we have to separate each section by element that we can identify as a different section. To make us ease on creating an auto focus on the navigation related to the section user reading at, we can put the same identity on section and navigation, respectively.

Building The Page

The single page we want to create consists of navigation and section. Each of them has five items, home, about, blog, contact, credits. Home on navigation related to home on section, so on. Each item on navigation identified using href attribut which is the same value as section that identified by id attribute.

Walking Navigation
content
content
content
content
content
#header-wrapper {
	display: block;
	position: fixed;
	width: 100%;
	height: 60px;
	color: #fff;
	background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#cbcbcb));
	background-image: -webkit-linear-gradient(top, #fefefe, #cbcbcb);
	background-image:    -moz-linear-gradient(top, #fefefe, #cbcbcb); 
   background-image:     -ms-linear-gradient(top, #fefefe, #cbcbcb); 
   background-image:      -o-linear-gradient(top, #fefefe, #cbcbcb); 
   background-image:         linear-gradient(to bottom, #fefefe, #cbcbcb);
	box-shadow: rgba(214,214,214,0.6) 0px 6px 0px;
	color: #444;
	text-shadow: rgba(255,255,255,0.5) 1px 1px 0px;
}

#content-wrapper {
	display: block;
	padding-top: 5em;
	color: #414141;
	line-height: 2em;
	text-shadow: #fff 1px 1px 0px;
	font-size: 15px;
}

	#home, #about, #blog, #tweet, #contact {
		display: block;
	}

.wrapper, #content-wrapper {
	margin: 0 auto;
	width: 960px;
	text-align: left;
}

	.navigation {
		font-size: 13px;
		text-transform: uppercase;
		display: inline-block;
		margin: 0;
		list-style-type: none;
		vertical-align: top;
	}
	
		.navigation li {
			display: inline-block;
			width: 70px;
			height: 16px;
			padding: 22px 0;
			text-align: center;
			cursor: pointer;
		}
			
		.navigation li:hover, .selected-nav {
			background-image: -webkit-gradient(linear, left top, left bottom, from(#7bbbf7), to(#338cdf));
			background-image: -webkit-linear-gradient(top, #7bbbf7, #338cdf);
			background-image:    -moz-linear-gradient(top, #7bbbf7, #338cdf); 
   		background-image:     -ms-linear-gradient(top, #7bbbf7, #338cdf); 
   		background-image:      -o-linear-gradient(top, #7bbbf7, #338cdf); 
   		background-image:         linear-gradient(to bottom, #7bbbf7, #338cdf);
			border-bottom: 6px solid #1f6db6;
		}
			
			.navigation li a {
				position: relative;
				z-index: 1px;
				color: #444;
				text-decoration: none;
			}
			
			.navigation li:hover a, .selected-nav a, .selected-nav2 a, .top a {
				color: #fff !important;
				text-shadow: #335f87 1px 1px 0px;
			}
	

Notice that the header-wrapper has position fixed, this aim to make the navigation always follow user scroll through the page.



Simple Style

.scroll() is a jQuery API to read user’s scroll on an element, in this context we will bind them to the window element. After reading the scroll we check its current position relative to the top of the page (scrollPosition) , checking through each navigation’s item and check the section which is closest to the scrollPosition. If the section found add customized style to make it look like selected.

//Bootstraping variable
headerWrapper	= parseInt($('#header-wrapper').height());
offsetTolerance	= 40;
	
//Detecting user's scroll
$(window).scroll(function() {
	
	//Check scroll position
	scrollPosition	= parseInt($(this).scrollTop());
	
	//Move trough each menu and check its position with scroll position then add selected-nav class
	$('.navigation a').each(function() {

		thisHref		= $(this).attr('href');
		thisTruePosition	= parseInt($(thisHref).offset().top);
		thisPosition 	= thisTruePosition - headerWrapper - offsetTolerance;
		
		if(scrollPosition >= thisPosition) {
		
		$('.selected-nav').removeClass('selected-nav');
		$('.navigation a[href='+ thisHref +']').parent('li').addClass('selected-nav');
		
		}
	});
});

If the last section is too close to the bottom of the page we can’t read the section using previous technique, to make it works we have to check we’re at the bottom of the page by subtract document height with window height then compare with scrollPosition, if it equal or scrollPosition more than bottomPage then we’re at the bottom of the page

//If we're at the bottom of the page, move pointer to the last section
bottomPage	= parseInt($(document).height()) - parseInt($(window).height());
	
if(scrollPosition == bottomPage || scrollPosition >= bottomPage) {
	
	$('.selected-nav').removeClass('selected-nav');
	$('.navigation a:last').parent('li').addClass('selected-nav');
}




Check out the demo 1 for this code.


Sliding Style

Another slick way to tell the user is by sliding a pointer through the navigation. From previous code we need to add an image element acting as pointer. This pointer will be move to the left or to the right pointing current section.

	//Bootstraping variable
	headerWrapper		= parseInt($('#header-wrapper').height());
	offsetTolerance	= 40;
	pointer				= $('.pointer');
	firstNav				= $('.navigation a:first').parent('li');
	defaultPointer		= firstNav.offset().left;
	
	//Move pointer to the first menu
	pointer.css('left', defaultPointer);
	firstNav.addClass('selected-nav2');
	
	//Detecting user's scroll
	$(window).scroll(function() {
	
		//Check scroll position
		scrollPosition	= parseInt($(this).scrollTop());
		
		//Move trough each menu and check its position with scroll position then move the pointer
		$('.navigation a').each(function() {

			thisHref				= $(this).attr('href');
			thisTruePosition	= parseInt($(thisHref).offset().top);
			thisPosition 		= thisTruePosition - headerWrapper - offsetTolerance;
			thisNav				= $('.navigation a[href='+ thisHref +']').parent('li');
			currentPosition	= parseInt(thisNav.offset().left);
			
			if(scrollPosition >= thisPosition) {
			
				$('.selected-nav2').removeClass('selected-nav2');
				pointer.stop().animate({'left': currentPosition});
				thisNav.addClass('selected-nav2');
			}
			
		});
		
		//If we're at the bottom of the page, move pointer to the last section
		bottomPage			= parseInt($(document).height()) - parseInt($(window).height());
		lastNav				= $('.navigation a:last').parent('li');
		currentPosition	= lastNav.offset().left;
		
		if(scrollPosition == bottomPage || scrollPosition >= bottomPage) {
			
			$('.selected-nav2').removeClass('selected-nav2');
			pointer.stop().animate({'left': currentPosition});
			lastNav.addClass('selected-nav2');
		}
		
	});

The difference with previous code is we are playing on the pointer rather than only styling the navigation. We check through each navigation’s item and get selected item’s left position relative to the window (currentPosition), finally slide the pointer to the currentPosition by adjusting its left. Check out the demo 2 for this code.



Cube Style

If you read my previous post you have to be familiar with this style. On this style focusing the navigation is using cube rotation, up to down direction. Just like previous post, we have to modify each navigation’s item by adding front side and top side. Front side is the default style and top side is selected style.

		.cube {
			-webkit-perspective : 1200px;
			-moz-perspective : 1200px;
			-ms-perspective : 1200px;
			perspective : 1200px;
			padding: 0 !important;
		}
		
		.cube:hover a {
			background: 0;
			color: 0;
			text-shadow: 0;
		}
		
			.cube-wrapper-show {
				-webkit-transform-style: preserve-3d;
				-moz-transform-style: preserve-3d;
				-ms-transform-style: preserve-3d;
				transform-style: preserve-3d;
				-webkit-animation: showMenu 0.5s ease-in-out;
				-moz-animation: showMenu 0.5s ease-in-out;
				-ms-animation: showMenu 0.5s ease-in-out;
				height: 60px;		
			}
			
			.cube-wrapper-hide {
				-webkit-transform-style: preserve-3d;
				-moz-transform-style: preserve-3d;
				-ms-transform-style: preserve-3d;
				transform-style: preserve-3d;
				-webkit-animation: hideMenu 0.5s ease-in-out;
				-moz-animation: hideMenu 0.5s ease-in-out;
				-ms-animation: hideMenu 0.5s ease-in-out;
				height: 60px;		
			}
			
				.top, .front {
					display: block;
					width: 70px;
					height: 16px;
					padding: 22px 0;
					vertical-align: middle;
					position: absolute;
				}
			
				.front {
					background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#cbcbcb));
					background-image: -webkit-linear-gradient(top, #fefefe, #cbcbcb);
					background-image:    -moz-linear-gradient(top, #fefefe, #cbcbcb); 
   				background-image:     -ms-linear-gradient(top, #fefefe, #cbcbcb); 
   				background-image:      -o-linear-gradient(top, #fefefe, #cbcbcb); 
   				background-image:         linear-gradient(to bottom, #fefefe, #cbcbcb);
				}
				
				.front-before-rotate {
					box-shadow: rgba(214,214,214,0.6) 0px 6px 0px;
					-webkit-transform:  translateZ(25px);
					-moz-transform:  translateZ(25px);
					-ms-transform:  translateZ(25px);
					transform:  translateZ(25px);
				}
			
				.top {
					background-image: -webkit-gradient(linear, left top, left bottom, from(#7bbbf7), to(#338cdf));
					background-image: -webkit-linear-gradient(top, #7bbbf7, #338cdf);
					background-image:    -moz-linear-gradient(top, #7bbbf7, #338cdf); 
   				background-image:     -ms-linear-gradient(top, #7bbbf7, #338cdf); 
   				background-image:      -o-linear-gradient(top, #7bbbf7, #338cdf); 
   				background-image:         linear-gradient(to bottom, #7bbbf7, #338cdf);
					border-bottom: 6px solid #1f6db6;	
					-webkit-transform: rotate3d(1,0,0,90deg) 
											 translateZ(30px);
					-moz-transform: rotate3d(1,0,0,90deg) 
											 translateZ(30px);
					-ms-transform: rotate3d(1,0,0,90deg) 
											 translateZ(30px);
					transform: rotate3d(1,0,0,90deg) 
											 translateZ(30px);
				}
				
				.hide-front {
					display: none;
				}

				.show-front {
					display: block;
				}

				.show-top {
					display: block;
				}
				
				.showed-top {
					display: block;
					-webkit-transform: rotate3d(1,0,0,0deg);
					-moz-transform: rotate3d(1,0,0,0deg);
					-ms-transform: rotate3d(1,0,0,0deg);
					transform: rotate3d(1,0,0,0deg);
				}

				.hidden-top {
					-webkit-transform:	rotate3d(1,0,0,90deg) 
												translateZ(30px);
					-moz-transform:	rotate3d(1,0,0,90deg) 
												translateZ(30px);
					-ms-transform:	rotate3d(1,0,0,90deg) 
												translateZ(30px);
					transform:	rotate3d(1,0,0,90deg) 
												translateZ(30px);
				}

@-webkit-keyframes showMenu {
	0% { -webkit-transform: rotate3d(1,0,0,0); }
	100% { -webkit-transform: rotate3d(1,0,0,-90deg); }
}

		
@-webkit-keyframes hideMenu {
	0% { -webkit-transform: rotate3d(1,0,0,-90deg); }
	100% { -webkit-transform: rotate3d(1,0,0,0); }
}

@-moz-keyframes showMenu {
	0% { -moz-transform: rotate3d(1,0,0,0); }
	100% { -moz-transform: rotate3d(1,0,0,-90deg); }
}

		
@-moz-keyframes hideMenu {
	0% { -moz-transform: rotate3d(1,0,0,-90deg); }
	100% { -moz-transform: rotate3d(1,0,0,0); }
}

@-ms-keyframes showMenu {
	0% { -ms-transform: rotate3d(1,0,0,0); }
	100% { -ms-transform: rotate3d(1,0,0,-90deg); }
}

		
@-ms-keyframes hideMenu {
	0% { -ms-transform: rotate3d(1,0,0,-90deg); }
	100% { -ms-transform: rotate3d(1,0,0,0); }
}

After checking through each navigation’s item, we add cube-wrapper-show class that trigger the CSS animation to rotate the cube-wrapper and hide other items by adding cube-wrapper-hide. When the animation done we push the displayed side to be displayed (see the reason).

	//Bootstrapping variable
	headerWrapper		= parseInt($('#header-wrapper').height());
	offsetTolerance	= 40;
	allNavigation		= $('.section');
	arrayNavigation	= new Array;
	bottomPage			= parseInt($(document).height()) - parseInt($(window).height());

	//Get all menu based on section
	allNavigation.each(function() {
		
		thisNav					= $(this).attr('id');
		thisHref					= $('a[href=#'+thisNav+']').attr('href');
		thisTruePosition		= parseInt($(thisHref).offset().top);
		thisPosition		 	= thisTruePosition - headerWrapper - offsetTolerance;
		
		arrayNavigation.push(thisPosition);
		
	});
	
	//Detecting user's scroll
	$(window).scroll(function() {
		
		//Check scroll position and get displayed section
		scrollPosition	= parseInt($(this).scrollTop());
		thisPosition	= 0;
		
		for(var i=0; i < arrayNavigation.length; i++){
		
			if(scrollPosition >= arrayNavigation[i]) {
				thisPosition =  i;
			}
		}
		
		//If we're at the bottom of the page, select last section
		if(scrollPosition == bottomPage || scrollPosition >= bottomPage) {
			thisPosition = parseInt(arrayNavigation.length) - 1;
		}
		
		//Preparing menu to be displayed
		currentElem			= $('.cube:eq('+thisPosition+')');
		currentCube			= currentElem.children('.cube-wrapper');
		currentFront		= currentCube.children('.front');
		currentTop			= currentCube.children('.top');
		
		//Get last displayed menu
		showedElem			= $('.cube:lt('+thisPosition+'), .cube:gt('+thisPosition+')');
		showedCube			= showedElem.children('.cube-wrapper');
		showedFront			= showedCube.children('.front');
				
		if(!currentCube.hasClass('cube-wrapper-show')) {
			
			//Hide other displayed menu
			showedCube.each(function() {
				
				if($(this).hasClass('cube-wrapper-show')) {
						
					$(this).children('.front').removeClass('hide-front');
					$(this).removeClass('cube-wrapper-show').addClass('cube-wrapper-hide');
					$(this).children('.top').removeClass().addClass('top');
				}
			
			});	
			
			//Show selected menu
			currentFront.addClass('front-before-rotate');
			currentCube.addClass('cube-wrapper-show');
			
			//Clean up after animation end
			if($.browser.webkit) {
			
				currentCube.bind('webkitAnimationEnd', function() {
				
					//Clean up last displayed menu
					showedCube.removeClass().addClass('cube-wrapper');
					showedFront.removeClass('front-before-rotate');
				
					//Force menu to be displayed
					currentTop.addClass('showed-top');
					currentFront.addClass('hide-front');
						
					$(this).unbind();
					
				});

			} else {
				
				setTimeout(function(){
					
					showedCube.removeClass().addClass('cube-wrapper');
					showedFront.removeClass('front-before-rotate');
				
					//Force menu to be displayed
					currentTop.addClass('showed-top');
					currentFront.addClass('hide-front');
					
				}, 500);
			}				
		}
	});	

Check out the demo 3 for this code.



We’re Done

Ton of single page website with their unique menu spread out there, all of them can inspire us the navigation design and behavior, maybe you have one? Share yours or your thought!

 

This Post Tags :

Bookmark Post :

Pin It
  • saran: gimana kalo pas diklik menu di header, kontennya sliding..
    terus juga, kalo di contoh 1, bagian contact gak pernah dapet. soalnya nanggung keatas kena blog, kebawah kena credits.
    begitupun pas klik menu contact. langsung ke credits.

    tadinya pengen komentar pake bahasa inggris, tapi terlalu ribet. :) ))

    btw, idenya bagus.. :D

    • Mantab mam :-bd
      Emang tadinya mau ditambah sliding ke konten tapi gw pikir jadi gak fokus pembahasannya ;)

  • I’ve seen a jQuery plugin called “ScrollSpy” from Bootstrap working in a similar way. Thanks

  • There is also a jquery plugin called “Waypoint” that you can use to achieve this. But also much more.

  • Very nice and very useful example. Thanks.

  • super effects
    thank you