When designing websites, I like to focus on ease of use and accessibility for the end user. While creating your site to be friendly to screen readers and text-based browsers is a must, the accessibility I’m referring to is making it easy for your audience to navigate your site and perform certain common actions. By providing an easy interface for your users, you are immediately increasing your chances that they’ll return for more of your site’s goodness.
Thus far in our “Web Development” blog series, we’ve looked at JavaScript Optimization, HTML5 Custom Data Attributes, HTML5 Web Fonts and using CSS to style the Highlight Selection. In this post, we’re going to create a “sticky” menu at the top of a page. As a user scrolls down, the menu will “stick” to the top and always be visible (think of Facebook’s Timeline view), allowing the user quicker access to clicking common links. With some simple HTML, CSS and JavaScript, you can have a sticky menu in no time.
Let’s start with our HTML. We’re going to have a simple header, menu and content section that we’ll throw in our <body>
tag.
<header> <h1>My Header</h1> </header> <nav id="menu"> <ul id="menu-list"> <li>Items</li> </ul> </nav> <div id="content"> Some content </div>
For brevity, I’ve shortened the content I show here, but the working example will have all the information. Now we can throw in some CSS to style our elements. The important part here is how the <nav>
is styled.
nav#menu { background: #FFF; clear: both; margin: 40px 0 80px 0; width: 99.8%; z-index: 2; } ul#menu-list li { border: solid 1px blue; list-style-type: none; display: inline-block; margin: 0 -3px; padding: 4px 10px; width: auto; }
We have set the menu’s background
to white (#FFF
) and given it a z-index
of 2
so that when the user scrolls, the menu will stay on top and not be see-through. We’ve also set the list items to be styled inline-block
, but you can style your items however you desire.
Now we get to the fun part – the JavaScript. I’ve created a class using Mootools, but similar functionality could be achieved using your favorite JavaScript framework. Let’s examine our initialize
method (our constructor) in our Stickit
class.
var Stickit = this.Stickit = new Class({ initialize: function(item, options) { // 'item' is our nav#menu in this case this.item = document.id(item); // The element we're scrolling will be the window this.scrollTarget = document.id(options.scrollTarget || document.window); // The 'anchor' is an empty element that will always keep the same location // when the user scrolls. This is needed because this.item will change and // we cannot rely on it for accurate calculations. this.anchor = new Element('div').inject(this.item, 'top'); // The 'filler' is an empty element that we'll use as a space filler for when // the 'item' is being manipulated - this will prevent the content below from // jumping around when we scroll. this.filler = new Element('div').inject(this.item, 'after'); // Set the styles of our 'filler' to match the styles of the 'item' this.setFillerStyles(); // Initialize our scroll events – see the next code section for details this.initEvents(); } });
What we’re doing here is grabbing our element to stick to the top – in this case, nav#menu
– and initializing our other important elements. I’ll review these in the next code section.
var Stickit = this.Stickit = new Class({ ... initEvents: function() { var that = this, // Grab the position of the anchor to be used for comparison during vertical scroll anchorOffsetY = this.anchor.getPosition().y, // Grab our original styles of our 'item' so that we can reset them later originalStyles = this.item.getStyles('margin-top', 'position', 'top'); // This is the function we'll provide as our scroll event handler var stickit = function(e) { // Determine if we have scrolled beyond our threshold - in this case, our // anchor which is located as the first element of our 'item' var targetScrollY = that.scrollTarget.getScroll().y, fixit = targetScrollY > anchorOffsetY; if (fixit && that.cache != 'fixed') { // If we have scrolled beyond the threshold, fix the 'item' to the top // of the window with the following styles: margin-top, position and top that.item.setStyles({ 'margin-top': 0, position: 'fixed', top: 0 }); // Show our (empty) filler so that the content below the 'item' does not // jump - this would otherwise be distracting to the user that.filler.setStyle('display', 'block'); // Cache our value so that we only set the styles when we need to that.cache = 'fixed'; } else if (!fixit && that.cache != 'default') { // We have not scrolled beyond the threshold. // Hide our filler that.filler.setStyle('display', 'none'); // Reset the styles to our 'item' that.item.setStyles(originalStyles); // Cache our values so we don't keep resetting the styles that.cache = 'default'; } }; // Add our scroll event to the target - the 'window' in this case this.scrollTarget.addEvent('scroll', stickit); // Fire our scroll event so that all the elements and styles are initialized this.scrollTarget.fireEvent('scroll'); } });
This method contains the meat of our functionality. The logic includes that we test how far the user has scrolled down on the page. If s/he scrolls past the threshold – in this case, the anchor which is located at the very top of the “stuck” item – then we set the menu to be fixed to the top of the page by setting the CSS values for margin-top
, position
and top
. We also display a filler so that the content below the menu doesn’t jump when we set the menu’s position
to fixed
. When the user scrolls back to the top, the styles are reset to their original values and the filler is hidden.
To see a full working example, check out this fiddle. The Stickit
class I created is flexible enough so that you can “stick” any element to the top of the page, and you can specify a different scrollTarget
, which will allow you to scroll another element (besides the window
) and allow the item to stick to the top of that element instead of the window
. If you want to give that a try, you can specify different options in Stickit
and modify your CSS as needed to get it working as you’d like.
Happy coding,
-Philip