Vertical Scroll Menu with jQuery Tutorial

Written by Kevin Liew on 02 Jun 2009
202,777 Views • Tutorials


Just last week, I came accross to this website Narrow Design. His scroll menu caught a lot of my attentions, I played with it for a while. Yes, unfortunately, it's built in flash. And, Yes, we are going to implement it with jquery - javascript based scroll menu that will do the same thing. Of course, it will not be 100% the same, because some of the fancy features just not that practical to implement with javascript.


So, in this tutorial, we will learn how to create a scroll menu. We will achieve the following objectives:

  • Keep html as simple as possible, and let jQuery and CSS do the rest.
  • Scoll up and down according to mouse-Y axis
  • Use jQuery.color plugin to animate the background-color changes - download jQuery.color
    (Yes, you will need this to animate the background color, I thought it will do it by default, obviously it doesn't! )

Just before we start, let me explain 3 important jquery methods we're going to use:

1. Get mouse axis

The following code will return the X and Y Axis values for your mouse pointer.

$(document).mousemove(function(e) {
$('#mouse_axis').html("X Axis : " + e.pageX + " | Y Axis " + e.pageY);

<div id="mouse_axis"></div>
2. Get objects offset

The following code will get the offset Top and Left for an object.

$(document).ready(function() {	
$('#offset').html("Top : " + $('#sidebar').offset().top + " |Left " 
+ $('#sidebar').offset().left);

<div id="offset"></div>

<div id="Sidebar">A Empty DIV named sidebar</div>
3. Get the total of selected elements by the selector.

It will return the total of selected elements.

$('#menu li').length;

Graphical Explanation

Please refer to the following div structure:

Structure for jQuery Scroll Menu

2 main DIVs #sidebar and #menu:

#sidebar : its overflow property is set to hidden. Overflow set to hidden will truncate/hide #menu's extra length and display the #menu according to the width and height of the #sidebar.

#menu : its position property is set to relative. So that if top property set to 0, #menu will snap to the top of #sidebar. So, even with no javascript, you can test the menu with random negative numbers, for example -30px, -100px or -500px. You will able to see the menu is going up. So, jQuery's job is to generate this negative values. To dynamicly generate these values, we will use the mouse-Y, because we want to scroll it up and down. It's quite complicated to explain, but we will walk through it in javascript section.


As usual, we always keep the HTML code as simple as possible. It's good to not mix javascript with html code to increase readibility and tidiness.

The SPAN in this example can be taken out, I put it in just to immitate the menu from Narrow Design.

<div id="sidebar">
<ul id="menu">
<li><a href="#">MENU 1 <span> / 2007</span></a></li>
<li><a href="#">MENU SIZE 2 <span> / 2007</span></a></li>
<li><a href="#">MENU SIZE LONG 3 <span> / 2007</span></a></li>
<li><a href="#">MENU 4 <span> / 2007</span></a></li>
<li><a href="#">MENU SIZE 5 <span> / 2007</span></a></li>
<li><a href="#">MENU SIZE LONG 6 <span> / 2007</span></a></li>

2. CSS

I have played with this CSS for quite a while to achieve the effect I want and tested it with IE as well. I was having this IE problem, where the position:relative and overflow:hidden just won't work the way it should. Fortunately, I found the solution through this website - solution to position relative and overflow in IE. Bingo, it displays exactly the same now.

#sidebar has to set as overflow:hidden to make sure the extra length in the menu is hidden. And the rest is just basic styling for the menu.

body {
margin:0 20px;

#sidebar {

#menu {

#menu li {
padding:10px 0;

#menu li a {
background:url() repeat #1f1f1f;
font-family:helvetica, arial, verdana;
padding:20px 8px 5px 20px;

#menu li span {
font-family:georgia, arial;

3. Javascript

In javascript section, I have separated all the configurable variables on the top of the script. It'll be easier to convert it to a plugin.

The most important part of this script is the last section - generate the top value based on the mouse Y value to allow user to scroll through the entire menu, and won't be affected by the offset of the sidebar as well. The mathematic equation I'm using, it's not the perfect one, but it works. If you have better suggestions, please drop me a message. : )

$(document).ready(function() {	

	//Background color, mouseover and mouseout
	var colorOver = '#31b8da';
	var colorOut = '#1f1f1f';

	//Padding, mouseover
	var padLeft = '20px';
	var padRight = '20px'
	//Default Padding
	var defpadLeft = $('#menu li a').css('paddingLeft');
	var defpadRight = $('#menu li a').css('paddingRight');
	//Animate the LI on mouse over, mouse out
	$('#menu li').click(function () {	
		//Make LI clickable
		window.location = $(this).find('a').attr('href');
	}).mouseover(function (){
		//mouse over LI and look for A element for transition
		.animate( { paddingLeft: padLeft, paddingRight: padRight}, { queue:false, duration:100 } )
		.animate( { backgroundColor: colorOver }, { queue:false, duration:200 });

	}).mouseout(function () {
		//mouse oout LI and look for A element and discard the mouse over transition
		.animate( { paddingLeft: defpadLeft, paddingRight: defpadRight}, { queue:false, duration:100 } )
		.animate( { backgroundColor: colorOut }, { queue:false, duration:200 });
	//Scroll the menu on mouse move above the #sidebar layer
	$('#sidebar').mousemove(function(e) {

		//Sidebar Offset, Top value
		var s_top = parseInt($('#sidebar').offset().top);		
		//Sidebar Offset, Bottom value
		var s_bottom = parseInt($('#sidebar').height() + s_top);
		//Roughly calculate the height of the menu by multiply height of a single LI with the total of LIs
		var mheight = parseInt($('#menu li').height() * $('#menu li').length);
		//I used this coordinate and offset values for debuggin
		$('#debugging_mouse_axis').html("X Axis : " + e.pageX + " | Y Axis " + e.pageY);
		$('#debugging_status').html(Math.round(((s_top - e.pageY)/100) * mheight / 2));
		//Calculate the top value
		//This equation is not the perfect, but it 's very close	
		var top_value = Math.round(( (s_top - e.pageY) /100) * mheight / 2)
		//Animate the #menu by chaging the top value
		$('#menu').animate({top: top_value}, { queue:false, duration:500});



That's it. Make sure you check out the demo and download the source code to play with it. Last but not least, I need your support :) If you like this article, please help me to promote it by adding this post into your bookmark. Or you can subscribe to my RSS for more posts. Thanks!

Demo Download
Join the discussion

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.

Alan 12 years ago
Is there a way to have more than one of these on the same page?
Visual 12 years ago
Thanx for sharing.
Alvi 12 years ago
Thanks for this tut. I tried it myself but padding seems to be off in IE and Firefox. The padding between the menus, with the default settings, does not show up in IE and chrome at all. Is there a way around it?
Website Design 12 years ago
This is good example for website design. Code is optimized. No extra code. .. ...
Andrew Conlan 12 years ago
Love the styling on the demo
daniel 12 years ago
I started to play with this menu and I wish there is posibility to ad curent style identical to hover.. is that posible somehow?

Philff 12 years ago
great tutorial, I've been playing with it for a design on a site I'm working on.

however I've run into a few problems which I can't seem to work out.

Design wise, the # sidebar is set the visible as I wanted the menu to be seen moving up and down the page.. unlike in the demo where it gets cut off at the edges of the # sidebar

The problem is , How do I calm the animation down a little. I'm having the problem that with my mouse at the bottom of my page the Menu shoots off upwards into the stratosphere off screen, Where i would like it to stop at the top of the screen or atleast only partially go off.

the menu is only 7 categories long, and it seems that the animation is suited for a much longer list of categories.

Anybody have any ideas?
Philff 12 years ago
I seem to have solved my issue, by altering the values of line

var top_value = Math.round(( (s_top - e.pageY) /100) * mheight / 4);

Luukratief 11 years ago
Hi there kevin.

Im not really good with JS, but i am trying to make a cool portfolio website with your scroller.

You can find what i am trying to make here:

Now as you can see it has to be full height.
I scrolled to the comments on here and found this:

"@mrSeanG: Yes, you can do the full screen scroller. In fact, that was the first version, you have to use jQuery to set the height #sidebar and do it inside onresize event as well."

Now i understand what you are on about, but i simply cannot do this myself due to lack of experience.

Do you have Google talk maybe so we can chat a bit on there? Or could you send me the script for the fullscreen version?

I would be verry gratefull.
Oh besides that... color.js has an error on line 17, just so you know
Chris 11 years ago

If you disable the JavaScript, of course its not going to work. Its powered my JavaScript.!!
Joe 11 years ago
Thank you so much for the inspiration and code. I wanted to always fill the #sidebar box as the default formula makes the whole thing almost disappear when the mouse reaches the bottom. I took one of the formulas listed in these comments and altered it so the whole #sidebar stays filled with the menu. Here is the modified formula found on (or near) line 53:

var sidebar_height = $('#sidebar').height();
top_value = Math.round(( ((e.pageY - s_top) / sidebar_height * ((sidebar_height - mheight) - (sidebar_height - 50)))));

I also added "-50" to it as I think the height of the menu is not calculated correctly. Seeing the #sidebar is a fixed height it works perfectly with that additional 50 in there. It is just random, plucked from thin air so please don't ask how I worked it out.

Hope this helps others too.
Joe 11 years ago
Forget that last comment of mine. It started freaking out when I added a heap of links and then took the time to mathematically work it out. The formula I got from the other comment had a few things missing in my script anyway which prevented it from working nicely with few menus.

Make sure in the css stylesheet you set the menu height as 'auto' rather than 100%. Also found that mheight was nothing and so to with s_a_top. So here is the formula that does work for me with any amount of menu items. I have also added a conditional statement to only slide if the menu is larger than the container.

var sidebar_height = $('#sidebar').height();
var menu_height = $('#sidebar ul').height();
top_value = Math.round(( ((e.pageY - s_top) / sidebar_height * ((sidebar_height - menu_height)))));
if(menu_height < sidebar_height) {
top_value = 0;
Mat 11 years ago
I just create an indexhibit theme (see it at ) with this tutorial that is released for free.
Go there for more informations.

Thanks for all these (as always) really great tutorial.