The MoveUp Menu

Chris Coyier //

I got an email quite a while back from Dirk Tucholski who showed me a site called FLOWmarket. He was wondering about how the menu system worked. I thought it looked neat and so set out to build it how I would do it. The idea is that there is a long vertical menu of links, not all of which are visible. As you scroll your mouse up and down the visible area, the menu scrolls itself to reveal more menu items and highlight the link the mouse is currently over.

View Demo   Download Files

HTML

A typical menu:

<div id="menu">

	<ul>

		<li><a href="#">Nature</a></li>
		<li><a href="#">Receivability</a></li>
		<li><a href="#">Alone time</a></li>

 		<!-- etc -->

	</ul>

</div>

Starter CSS

We're setting a static height, so let's make sure the overflow value, for now, is set to overflow: auto; That way the menu will scroll and be accessible regardless of JavaScript. Otherwise just basic styling.

#menu { 
  height: 360px;
  overflow: auto;
}

#menu ul { 
  list-style: none; 
}

#menu a { 
  text-decoration: none; 
  display: block; 
  color: black; 
}

Starter JavaScript

The idea here is to wait for the menu links to be hovered over and adjust accordingly. We'll apply a hover class for styling, then move an inside div (which we'll append) up or down to acheive the effect. But just how far up or down? For that we'll need to know exactly which link we're hovering over. A higher position link (further down the list) needs to scroll the list farther than a lower position link. We'll take this position and apply a speed multiplier to get the distance to offset. To get position, we'll just loop through them and apply a data attribute.

$("#menu").css("overflow", "hidden").wrapInner("<div id='mover' />");

var $el,
    speed = 13.5,    // needs to be manually tinkered with
    items = $("#menu a");
    				
items
.each(function(i) {
	$(this).attr("data-pos", i);
})
.hover(function() {

	$el = $(this);
	$el.addClass("hover");	
	
	$("#mover").css("top", -($el.data("pos") * speed - 40));
	// 40 is the top padding for the fadeout
						
}, function() {
	$(this).removeClass("hover");
});

Note that you can access HTML5 data attributes (e.g. <div data-pos=1>) like $("div").data("pos"); which is succinct and cool.

Fadeouts

If you view the demo in a WebKit browser you'll see the menu fade out on top and bottom as it scrolls. I just did that by using :before and :after pseudo elements absolutely positioned at the top and bottom of the menu which have white-to-transparent gradients.

#menu:before { 
  content: " "; 
  position: absolute; 
  top: 0; 
  left: 0; 
  height: 50px;
  width: 100%; 
  z-index: 2; 
  background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, rgba(255,255,255,100)),color-stop(1, rgba(255,255,255,0))); 
}

#menu:after { 
  content: " "; 
  position: absolute; 
  bottom: 0; 
  left: 0; 
  height: 50px; 
  width: 100%; 
  z-index: 2;
  background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, rgba(255,255,255,0)),color-stop(1, rgba(255,255,255,100))); }

Use other browser prefixes as needed.

This "Speed" Business

The amount the menu is shifted up or down per menu item is affected by a speed multiplier. You can see in the above JavaScript that it's set to a rather arbitrary looking 13.5. I played for a while trying to figure out a mathematical way to calculate the perfect speed multiplier based on number of menu items and heights and whatnot but didn't get anything good. If you can think of something, let me know. It would definitely be better to not have to tinker with that multiplier every time the menu changes.

Keyboard Navigation

This menu, at the current speed, is a bit hard to navigate to an exact menu link you have picked out. To assist with that, we can add a little keyboard navigation. That is done by watching for keydown events on the document and firing off a function. To reduce rewriting code, we'll just keep track of the currently active menu item, adjust it up or down depending on if the up arrow or down arrow was pressed, then trigger the mouseenter or mouseleave events accordingly, which are already set up to handle the menu functionality.

$(document).keydown(function(event) {

	cur = $(".hover").attr("data-pos");
				
	// Down arrow
	if (event.keyCode == 40) {
									
		$("[data-pos=" + cur + "]").trigger("mouseleave");
		if (cur != max) { cur++; }
		$("[data-pos=" + cur + "]").trigger("mouseenter");
		
	}
	
	// Up arrow
	if (event.keyCode == 38) {
	
		$("[data-pos=" + cur + "]").trigger("mouseleave");
		if (cur > 0) { cur--; }
		$("[data-pos=" + cur + "]").trigger("mouseenter");
		
	}
	
});

The only bits not shown here is where we set up the cur variable and set it in the hover function, but all that is there in the live demo.

View Demo   Download Files

Rabble rabble usability!

The very core of this idea, scrolling a menu faster than a user would expect, is bad for usability. It's harder to pick out the exact link you are going for because of the unfamiliar and touchy movement. I am aware. The keyboard navigation helps somewhat there, but that's also potentially an unfamiliar idea.

This isn't a solution for every long menu in the world (also see here). This is more of a fun effect. Perhaps for a site where the menu isn't all that super important. Perhaps as a way to display a poem. It's a neat experience to use, so if that experience really works for the site, maybe a little accessibility hit is OK. I think the FLOWmarket site from whence this came is great.