Web Development - JavaScript - Creating a Sticky Menu

May 23, 2012

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 &amp;&amp; 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 &amp;&amp; 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

Comments

May 23rd, 2012 at 5:28pm

Thank you, Philip Thompson, for your detailed article.

While I visited the fiddle site, it was too complex for me to get an idea of the Stickit code.

Unless you are seeking only highly technical users, may I suggest the following?

Cover the when question. When would I use such code? (if you covered it, maybe it was too technical; how would a small business steward / owner figure it out).

Have an example of the code based on a site a small business user might visit, own, manage, or otherwise interact.

In addition to expanding your reader base to those less technical than you, you will also be expanding SoftLayer to more small businesses who can most likely benefit from what you and SoftLayer have to offer.

Thank you for your time and consideration.

May 24th, 2012 at 1:44am

This post is nice but there is some chaos about margin top so will you please tell me more about this.

May 24th, 2012 at 10:47am

Thanks for the comments, guys!

Peter, On the JSFiddle site, each of the four quadrants has a different piece of the puzzle. The top-left is the HTML code, the bottom-left is the Javascript that does the magic, the top-right is the CSS that styles the page, and the bottom-right is a live example of the code in action. If you scroll down in the "live example" quadrant, you'll see the "Just a header" part disappear while the "Something" menu stays at the top of the page regardless of how far you scroll.

The example he gave in the post was the menu that appears and sticks to the top of the page after you scroll past the Cover Photo + About section on a given Facebook profile or page in Timeline view, but that was more of a passing mention than a specific "Go look at this to see what I'm talking about" application of the code. The basic benefit is, "allowing the user quicker access to clicking common links."

Jacob, Can you explain a little more about what clarification you're looking for?

June 2nd, 2012 at 5:13pm

This is awesome, would be even cooler if you did a JQuery version too :)

July 11th, 2012 at 2:53am

Thank you Philip. It is explained in much detail and must admit it is a great effort. Keep posting.

January 13th, 2014 at 10:05am

Thanks for the code but it does not seem to cope with scrolling beyond the height of the contained parent as I want it to stick to the bottom of its parent when it cannot scroll beyond it, like on the iPhone. I have found another javascript sticky function that seems to cope with its parent bounds: http://www.andrewlowndes.co.uk/blog/front-end/sticky-css-_-js-function

Leave a Reply

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <pre>, <blockcode>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Comments

May 23rd, 2012 at 5:28pm

Thank you, Philip Thompson, for your detailed article.

While I visited the fiddle site, it was too complex for me to get an idea of the Stickit code.

Unless you are seeking only highly technical users, may I suggest the following?

Cover the when question. When would I use such code? (if you covered it, maybe it was too technical; how would a small business steward / owner figure it out).

Have an example of the code based on a site a small business user might visit, own, manage, or otherwise interact.

In addition to expanding your reader base to those less technical than you, you will also be expanding SoftLayer to more small businesses who can most likely benefit from what you and SoftLayer have to offer.

Thank you for your time and consideration.

May 24th, 2012 at 1:44am

This post is nice but there is some chaos about margin top so will you please tell me more about this.

May 24th, 2012 at 10:47am

Thanks for the comments, guys!

Peter, On the JSFiddle site, each of the four quadrants has a different piece of the puzzle. The top-left is the HTML code, the bottom-left is the Javascript that does the magic, the top-right is the CSS that styles the page, and the bottom-right is a live example of the code in action. If you scroll down in the "live example" quadrant, you'll see the "Just a header" part disappear while the "Something" menu stays at the top of the page regardless of how far you scroll.

The example he gave in the post was the menu that appears and sticks to the top of the page after you scroll past the Cover Photo + About section on a given Facebook profile or page in Timeline view, but that was more of a passing mention than a specific "Go look at this to see what I'm talking about" application of the code. The basic benefit is, "allowing the user quicker access to clicking common links."

Jacob, Can you explain a little more about what clarification you're looking for?

June 2nd, 2012 at 5:13pm

This is awesome, would be even cooler if you did a JQuery version too :)

July 11th, 2012 at 2:53am

Thank you Philip. It is explained in much detail and must admit it is a great effort. Keep posting.

January 13th, 2014 at 10:05am

Thanks for the code but it does not seem to cope with scrolling beyond the height of the contained parent as I want it to stick to the bottom of its parent when it cannot scroll beyond it, like on the iPhone. I have found another javascript sticky function that seems to cope with its parent bounds: http://www.andrewlowndes.co.uk/blog/front-end/sticky-css-_-js-function

Leave a Reply

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • You can enable syntax highlighting of source code with the following tags: <pre>, <blockcode>, <bash>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.