a Vertical Scrolling Menu with CSS3 and jQuery

Creating markup

We will begin by creating the necessary HTML structure. At first, I wanted to use an unordered list as a container for our images and captions, but then I encountered some bugs with vertical spacing between list items in Internet Explorer, and decided to use a container div instead. I also added the title attribute to each link to show a tooltip on hover. Internet Explorer ignore that attribute, and uses img alt text instead.

 

 

Menu
Navigation
jQuery       
CSS
Vertical
Menu
Navigation   

 

Adding basic styling

Now we will add some some CSS rules. It’s better to put them into an external style sheet, but for demonstration purpose I will put them directly into HTML document.

div.sc_menu_wrapper {
position: relative;  
height: 500px;
/* Make bigger than a photo, because we need a place for a scroll-bar. */
width: 160px;

margin-top: 30px;
overflow: auto;
}
div.sc_menu {
padding: 15px 0;
}
.sc_menu a {
display: block;
margin-bottom: 5px;
width: 130px;

border: 2px rgb(79, 79, 79) solid;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;

/* When image support is turned off */
color: #fff;
background: rgb(79, 79, 79); 
}
.sc_menu a:hover {
border-color: rgb(130, 130, 130);
border-style: dotted;
}
.sc_menu img {
display: block;
border: none;
}

The “overflow” property is used to add a scroll-bar to the wrapper div. We will need the “position” property to position our tooltips. The “-webkit-border-radius” and “-moz-border-radius” properties add a rounded corners for Firefox, Safari and Chrome. Unfortunately, Internet Explorer doesn’t support it and will display regular corners instead.

jQuery

We will need to add a jQuery to our document first. I use version hosted on Google API, because it is often already saved in the browser cache which noticeably reduces page load time.

$() is a shorthand for $(document).ready(), the most commonly used jQuery function. It allows you to bind a function to be executed when the DOM document has finished loading. The “makeScrollable” function will contain all code required to make our menu scrollable, you should put this function into external file.

function makeScrollable(wrapper, scrollable){
}
$(function(){  
makeScrollable("div.sc_menu_wrapper", "div.sc_menu");
});

Now, we will add a code that will display a loader while images are loading. The “enable” function will be fired when all images will be loaded.

function makeScrollable(wrapper, scrollable){
// Get jQuery elements
var wrapper = $(wrapper), scrollable = $(scrollable);

// Hide images until they are not loaded
scrollable.hide();
var loading = $("

").appendTo(wrapper);

// Set function that will check if all images are loaded
var interval = setInterval(function(){
var images = scrollable.find("img");
var completed = 0;

// Counts number of images that are succesfully loaded
images.each(function(){
if (this.complete) completed++;  
});

if (completed == images.length){
clearInterval(interval);
// Timeout added to fix problem with Chrome
setTimeout(function(){

loading.hide();
// Remove scrollbars   
wrapper.css({overflow: "hidden"});

scrollable.slideDown("slow", function(){
enable();
});                
}, 1000);
}
}, 100);

function enable(){           
}
}

Let’s style our loading message with the CSS.

.sc_menu_wrapper .loading {
position: absolute;
top: 50px;
left: 10px;

margin: 0 auto;
padding: 10px;

width: 100px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;

text-align: center;
color: #fff;
border: 1px solid rgb(79, 79, 79);
background: #1F1D1D;
}

Making menu scrollable

We will use the “mousemove” event to bind a function to be fired when the mouse is moved over menu.

The event’s “pageY” attribute returns the horizontal coordinate of the mouse relative to the whole document, but we need position relative to the wrapper div, so we will subtract “wrapperOffset.top” from it.

List must scroll faster than the mouse is moved to make that we use “(scrollableHeight – wrapperHeight) / wrapperHeight” proportion.

Here is our new enable function:

function enable(){
// height of area at the top at bottom, that don't respond to mousemove
var inactiveMargin = 100;        
// Cache for performance
var wrapperWidth = wrapper.width();
var wrapperHeight = wrapper.height();
// Using outer height to include padding too
var scrollableHeight = scrollable.outerHeight() + 2*inactiveMargin;
// Do not cache wrapperOffset, because it can change when user resizes window
// We could use onresize event, but it's just not worth doing that
// var wrapperOffset = wrapper.offset();

//When user move mouse over menu         
wrapper.mousemove(function(e){
var wrapperOffset = wrapper.offset();
// Scroll menu
var top = (e.pageY -  wrapperOffset.top) * (scrollableHeight - wrapperHeight) / wrapperHeight  - inactiveMargin;

if (top < 0){
top = 0;
}

wrapper.scrollTop(top);
});      
}

Styling Tooltips (optional)

Here is the CSS code to style our tooltips:

/* Styling tooltip */
.sc_menu_tooltip {
display: block;
position: absolute;

padding: 6px;
font-size: 12px; 
color: #fff;

-webkit-border-radius: 4px;
-moz-border-radius: 4px;

border: 1px solid rgb(79, 79, 79);
background: rgb(0, 0, 0);
/* Make background a bit transparent for browsers that support rgba */   
background: rgba(0, 0, 0, 0.5);
}

And here is the complete enable function with code that will move tooltip and change text in it. When I started to write this code I tried to use jQuery “hover” event, but it noticeably decreased performance. That’s why I had to use this strange approach. I made a lot of comments in the code, so I hope it won’t be too difficult for you to understand it.

function enable(){ 
// height of area at the top at bottom, that don't respond to mousemove
var inactiveMargin = 100;    
// Cache for performance
var wrapperWidth = wrapper.width();
var wrapperHeight = wrapper.height();
// Using outer height to include padding too
var scrollableHeight = scrollable.outerHeight() + 2*inactiveMargin;
// Do not cache wrapperOffset, because it can change when user resizes window
// We could use onresize event, but it's just not worth doing that
// var wrapperOffset = wrapper.offset();

// Create a invisible tooltip
var tooltip = $('<div class="sc_menu_tooltip"></div>')
.css('opacity', 0)
.appendTo(wrapper);

// Save menu titles
scrollable.find('a').each(function(){            
// The "data" function attaches custom data to an element
$(this).data('tooltipText', this.title);               
});

// Remove default tooltip
scrollable.find('a').removeAttr('title');    
// Remove default tooltip in IE
scrollable.find('img').removeAttr('alt');

var lastTarget;
//When user move mouse over menu         
wrapper.mousemove(function(e){
// Save target
lastTarget = e.target;

var wrapperOffset = wrapper.offset();

var tooltipLeft = e.pageX - wrapperOffset.left;
// Do not let tooltip to move out of menu.
// Because overflow is set to hidden, we will not be able too see it
tooltipLeft = Math.min(tooltipLeft, wrapperWidth - 75); //tooltip.outerWidth());

var tooltipTop = e.pageY - wrapperOffset.top   wrapper.scrollTop() - 40;
// Move tooltip under the mouse when we are in the higher part of the menu
if (e.pageY - wrapperOffset.top < wrapperHeight/2){
tooltipTop  = 80;
}              
tooltip.css({top: tooltipTop, left: tooltipLeft});

// Scroll menu
var top = (e.pageY -  wrapperOffset.top) * (scrollableHeight - wrapperHeight) / wrapperHeight  - inactiveMargin;

if (top < 0){
top = 0;
}
wrapper.scrollTop(top);
});

// Setting interval helps solving perfomance problems in IE
var interval = setInterval(function(){
if (!lastTarget) return;

var currentText = tooltip.text();

if (lastTarget.nodeName == 'IMG'){                 
// We've attached data to a link, not image
var newText = $(lastTarget).parent().data('tooltipText');

// Show tooltip with the new text
if (currentText != newText) {
tooltip
.stop(true)
.css('opacity', 0)   
.text(newText)
.animate({opacity: 1}, 1000);
}                
}
}, 200);

// Hide tooltip when leaving menu
wrapper.mouseleave(function(){
lastTarget = false;
tooltip.stop(true).css('opacity', 0).text('');
});          
}

 

Leave a comment