Submit your widget

a Vertical Scrolling Menu with CSS3 and jQuery

Created 13 years ago   Views 34959   downloads 4791    Author valums
a Vertical Scrolling Menu with CSS3 and jQuery
View DemoDownload
140
Share |

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.

<div class="sc_menu_wrapper">
  <div class="sc_menu">
    <a title="Menu" href="#"><img src="img/1.jpg" alt="Menu"/></a>
    <a title="Navigation" href="#"><img src="img/2.jpg" alt="Navigation"/></a>
    <a title="jQuery" href="#"><img src="img/3.jpg" alt="jQuery"/></a>        
    <a title="CSS" href="#"><img src="img/4.jpg" alt="CSS"/></a>
    <a title="Vertical" href="#"><img src="img/5.jpg" alt="Vertical"/></a>
    <a title="Menu" href="#"><img src="img/1.jpg" alt="Menu"/></a>
    <a title="Navigation" href="#"><img src="img/2.jpg" alt="Navigation"/></a>    
  </div>
</div>

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.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js" type="text/javascript"></script>

$() 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 = $("<div class='loading'></div>").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&#39;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&#39;s just not worth doing that 
  // var wrapperOffset = wrapper.offset();

  // Create a invisible tooltip
  var tooltip = $(&#39;&lt;div class=&quot;sc_menu_tooltip&quot;&gt;&lt;/div&gt;&#39;)
    .css(&#39;opacity&#39;, 0)
    .appendTo(wrapper);

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

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

  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 &lt; 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 == &#39;IMG&#39;){                  
      // We&#39;ve attached data to a link, not image
      var newText = $(lastTarget).parent().data(&#39;tooltipText&#39;);

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

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