A couple weeks ago, I created a screencast that demonstrated how to build a three-level navigation menu. In a response email, one of our readers requested a tutorial on how to build a lava-lamp style menu. Luckily, it’s quite a simple task, especially when using a JavaScript library. We’ll build one from scratch today.
Before we can create this neat functionality, we need a base from which to work from. In your favorite code editor, create an unordered list for your navigation, and import both jQuery and jQuery UI, via Google.
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>SpasticNav Plugin</title> <link rel="stylesheet" href="/css/style.css" type="text/css" media="screen" /> </head> <body> <div id="container"> <ul id="nav"> <li id="selected"><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Blog</a></li> <li><a href="#">More About My Portfolio</a></li> <li><a href="#">Contact</a></li> </ul> </div> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script> </body> </html>
Note how we gave an id of “selected” to the home page. This is fairly standard in most websites; it allows use to target the current page, and style that particular list item accordingly.
Next, we must decide how to best implement the lava-lamp functionality. To allow for reusability, we’ll package this little script into a plugin, and call it like:
$('#nav').spasticNav();
Since we’ve decided to build a plugin, let’s go ahead and create a new file for that script, and reference it in our mark-up. We’ll call it jquery.spasticNav.js.
<script type="text/javascript" src="/js/jquery.spasticNav.js"></script>
<script type="text/javascript">
$('#nav').spasticNav();
</script>
</body>
To reduce the number of global variables that we must create, as well as remove any possibilities of the $ symbol clashing with other JavaScript libraries, let’s wrap our plugin in a self-executing anonymous function.
(function($) {
})(jQuery);
Now, jQuery will be passed into our plugin, and will be represented via the $ symbol.
Next, it’s generally a best practice to give the users of the plugin as much flexibility as possible. As such, we’ll give them the option of passing in an object-literal when they call the plugin to override a handful of settings. As I see it, they should be able to:
Now, we’ll name our plugin, and make it equal to a function. $.fn is simply an alias for jquery.prototype.
$.fn.spasticNav = function(options) {
};
Knowing that we’ll be allowing these overrides, we must make sure that we accept an “options” parameter.
Now that we’ve named our plugin, the next step is to create the configuration options.
options = $.extend({
overlap : 20,
speed : 500,
reset : 1500,
color : '#0b2b61',
easing : 'easeOutExpo'
}, options);
Above, we’re taking the options variable, setting some default properties and values, and then extending it with whatever (if anything) the user passes in when they call the plugin. That way, the options they pass will override our default settings. For example, if, when I call this plugin, I pass:
$('#nav').spasticNav({
speed : 2000,
easing : 'easeOutElastic'
});
Those two properties will override the default settings, while the remainder of the options will remain the same.
Now, we’re ready to cycle through each element that was passed to this plugin, and implement the lava-lamp functionality. Remember, we can’t assume that the user is going to pass a single element to this plugin. They could, if they wanted, reference a class, which refers to multiple items that should receive this functionality. As such, we’ll call this.each to iterate over each item in the wrapped set.
return this.each(function() {
});
Within this function, we’ll create some variables. Not all of them will immediately have values, but since the JavaScript engine will hoist all variable names to the top of the function anyways (behind the scenes), it’s generally a best practice to declare them at the top, and then initialize them later.
var nav = $(this),
currentPageItem = $('#selected', nav),
blob,
reset;
Now that we’ve declared/initialized our variables, let’s create the actual blob, so to speak.
$('<li id="blob"></li>').css({
width : currentPageItem.outerWidth(),
height : currentPageItem.outerHeight() + options.overlap,
left : currentPageItem.position().left,
top : currentPageItem.position().top - options.overlap / 2,
backgroundColor : options.color
}).appendTo(this);
The reason why we’re calling the CSS method, rather than simply adding a class, is because these values will vary depending on the current page’s list item. As such, we must use JavaScript to retrieve they values.
Finally, we append this new list item to this, or #nav.
Next, we need to store a reference to #blob. That way, we don’t have to search the DOM everytime we wish to access it. We declared the blob variable at the top of the function. Now, let’s initialize it.
blob = $('#blob', nav);
We must now “listen” for when the user hovers over one of the list items (excluding the blob of course) in our navigation menu. When they do, we’ll set the width and left properties of the blob equal to that of the currently hovered list item.
$('li:not(#blob)', nav).hover(function() {
// mouse over
clearTimeout(reset);
blob.animate(
{
left : $(this).position().left,
width : $(this).width()
},
{
duration : options.speed,
easing : options.easing,
queue : false
}
);
}, function() {
// mouse out
reset = setTimeout(function() {
blob.animate({
width : currentPageItem.outerWidth(),
left : currentPageItem.position().left
}, options.speed)
}, options.reset);
});
To summarize the script above…
And that’s all there is to it! This is a super simple plugin. The next step is to style our navigation menu.
Without any styling, our menu should look similar to this:
Let’s first style the “nav” ul. Open your style.css file, and add:
#nav {
position: relative;
background: #292929;
float: left;
}
Next, we’ll style each list item.
#nav li {
float: left;
list-style: none;
border-right: 1px solid #4a4a4a;
border-left: 1px solid black;
}
This simply floats each list item to the left, and adds a border to each side.
Moving along, we next must style the anchor tags within our navigation menu.
#nav li a {
color: #e3e3e3;
position: relative;
z-index: 2;
float: left;
font-size: 30px;
font-family: helvetica, arial, sans-serif;
text-decoration: none;
padding: 30px 45px;
}
We’re setting a color, floating them to the left, setting some font values, and a healthy amount of padding. Take note of the z-index property. This is a necessity, and will be explained shortly. However, remember that, in order to adjust the z-index, we must set a positioning context, which we’ve done.
Because we’re not implementing a full reset stylesheet, let’s ensure that we zero out any default margins and padding on our ul and li, just to save any potential headaches.
ul, li {
margin: 0; padding: 0;
}
The last step is to style the blob itself!
#blob {
border-right: 1px solid #0059ec;
border-left: 1px solid #0059ec;
position: absolute;
top: 0;
z-index : 1;
background: #0b2b61;
background: -moz-linear-gradient(top, #0b2b61, #1153c0);
background: -webkit-gradient(linear, left top, left bottom, from(#0b2b61), to(#1153c0));
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-moz-box-shadow: 2px 3px 10px #011331;
-webkit-box-shadow: 2px 3px 10px #011331;
}
Once again, we set some pretty colors for our borders, and add some background colors (including CSS3 gradients/borders/shadows for Firefox and Safari/Chrome). Once again, we see that z-index property. Without this, the blob will display above all of the text in the navigation menu. To counter this, we must be sure that its z-index property is LOWER than the list item’s! We also must set the position to absolute in order to adjust its top and left values with our plugin.
That’s all there is to it! With minimal effort, we’ve created a really neat looking navigation menu from scratch. Let me know if you have any questions! Thanks for reading and watching.