CodeinWP CodeinWP

Fixed Table of Contents Drop-Down Menu (jQuery Plugin)

Fixed Table of Contents Drop-Down Menu (jQuery Plugin)About a week or so ago, I stumbled across the Startups, This Is How Design Works website. It’s a one-page site that uses a fixed drop-down menu at the top of the screen that collapses/expands in a “table of contents” style.

I thought it was kinda cool, so I wrote my own script to create this functionality, and I turned it into a jQuery plugin. Use the button below to view the demo, and read on for a description.

You can also view the alternate demo that doesn’t use submenus.

The HTML is structured using unordered nested lists. I suppose I could have used ordered lists to facilitate maintainable numbering, but I thought it was fine this way and if someone wants to use ordered lists they can just alter the markup and make a small change in the script.

After you include the plugin in your page, here’s how you use it:

// call the plugin on the "#toc" element
$('#toc').fixedTOC({
  menuOpens: 'click',
  scrollSpeed: 1000,
  menuSpeed: 300,
  useSubMenus: true,
  resetSubMenus: true,
  topLinkWorks: true
});

The default settings are shown above. You don’t actually have to include any of these, this is just so you can see the available customizable options. And here’s a description of what they do:

menuOpens
The jQuery event that will open the menu. The two most logical options are click and mouseenter.

scrollSpeed
Speed of the animated scrolling, in milliseconds.

menuSpeed
Speed of the “dropping” menu and submenus, in milliseconds.

useSubMenus
If your menu doesn’t have any nested lists, which means no “submenus”, then set this to false. This will cause the main menu links to become active. By default, the main menu links don’t scroll to anywhere, they just open the submenus.

resetSubMenus
By default, each time you mouse off the menu, the menu collapses and any active submenu also collapses. If you want to keep the active submenu open, set this to false.

topLinkWorks
Finally, if your page doesn’t include the “top” link (which appears on the demo in the bottom right area of the page, letting the user scroll back to the top), set this to false.

Each of the hyperlinks in the menu needs to correspond to an ID on the page for the in-page-scroll part to actually work. And my demo is also responsive and slightly changes the look of the menu and “top” link on a small screen (although I haven’t really done too much testing for this).

If you have any suggestions or feedback, comment below or open an issue on the GitHub repo. I suppose it would be cool to expand the script to be able to parse the HTML and create the drop-down menu automatically like this script does. Also, I haven’t done anything special in the CSS to deal with JavaScript-off users, but you could easily add a “no-js” class to the body and use that CSS hook to do some stuff when JavaScript is off, and remove the class when JavaScript is on (as Modernizr does).

Update (Feb. 3/2013): Added this plugin to the new jQuery plugins repository. It was quite an easy process, highly recommended.

65 Responses

  1. Paul says:

    Hi

    How does the list get populated?

    Does the plugin find all header tags in your document or is it manual?

    Thanks
    Paul

    • No, as I mentioned in the article, ideally it would do that. But as it stands, you need to manually create the TOC using the same basic HTML structure that I’m using.

      I’m going to eventually look into updating the plugin to do this automatically, but for now this will have to do. And if anyone wants to tackle that, they can fork the project and have a go at it.

      In the last paragraph I link to a similar script that automatically creates a “sidebar” table of contents, so it might be worth looking into that one if you want the TOC to be fully visible on the side. I have noticed some odd bugs and behaviour in that script though, so I’m not sure how reliable that one is.

  2. Carlos Camacho says:

    On my iPad, when I select any item on the menu, the whole menu structure remains on top of the contents and momentarily cannot be selected. After I click on the content, the menu is once again “active” but still on top of the content. Maybe a simple modification could change that, so that the menu minimizes after you’re sent to the desired anchor… Would love to do that, but my jQuery skills are basic ;(

    • Hm. Interesting. I’ll have to put that on my to-do list and fix it when I get a chance. I don’t have access to an iPad right now, but I’m planning to get one soon. Thanks.

  3. Richard Razo says:

    Nice… reminds me of using an e-reader. I can use this for a simple website for a book teaser.

  4. Gautam Lakum says:

    Great dude! Really like it and tried it in one of my site.
    Thanks for such menu

  5. SFdude says:

    In my Firefox 12 (XP Pro SP3 32 bit):

    Clicking on the text:
    “Table of Contents” opens the dropdown menu
    and correctly exposes the menu items.

    But… when I try to select
    an item in the dropdown menu,
    for ex: “Breakfast”,
    the whole menu closes on me,
    (- collapses back to only: “Table of Contents”),
    before I have the chance to click on any item.

    Anybody else has this problem?

    SFdude

  6. DJERock says:

    What type of pathing changes or what changes period can I make to make the links dynamically load content (via Ajax) in an outer container as opposed to having it scroll the page with the anchor tags??

    I have an older set up I was working on @ this link:

    http://internetbusinesspotential.com/cdc/slidedown-menu2.html

    but I’m getting the same issue there, I can get it to load content already on the page but not new external html files in to an outer container. I have to pitch this in xhtml and html5 doctypes.

    If you can help with this work around I will repost the project and give you major cred… Either way Just let me know? Thanks ~E

  7. DJERock says:

    Oh yeah here is the call for content if you could just tell me how to point it:

    
    $(document).ready(function() {
    						   
    	var hash = window.location.hash.substr(1);
    	var href = $('#nav li a').each(function(){
    		var href = $(this).attr('href');
    		if(hash==href.substr(0,href.length-5)){
    			var toLoad = hash+'.html #content';
    			$('#content').load(toLoad)
    		}											
    	});
    
    	$('#nav ul li a').click(function(){
    								  
    		var toLoad = $(this).attr('href')+' #content';
    		$('#content').hide('fast',loadContent);
    		$('#load').remove();
    		$('#wrapper').append('LOADING...');
    		$('#load').fadeIn('normal');
    		window.location.hash = $(this).attr('href').substr(0,$(this).attr('href').length-5);
    		function loadContent() {
    			$('#content').load(toLoad,'',showNewContent())
    		}
    		function showNewContent() {
    			$('#content').show('normal',hideLoader());
    		}
    		function hideLoader() {
    			$('#load').fadeOut('normal');
    		}
    		return false;
    		
    	});
    
    });
    
    • I’m not entirely sure what you’re asking.

      You should just intercept the clicks on the links and instead of scrolling, just load the content in via Ajax. It looks like the code you’re using should work. So what exactly is the problem?

  8. R Krishana says:

    really very helpful, I will like to apply this in my site

  9. marc dakar says:

    very nice done,
    i wish to extend it by multilevel menue (a sub menu of an sub menu)
    is this possible, can you give me a hint where to start
    cheers marc

    • Hey, Marc. It’s possible to do that, but you’ll have to fiddle with the code and add the necessary markup. It’s not the most straightforward thing, based on how I did the code.

      I suppose I should have it as one of the plugin options to tell it how many levels deep you want to go, or it should work automatically based on reading the markup. I’ll see what I can do about updating it, but I probably won’t do anything with this for a while.

      If anyone wants to take a stab at it, they can fork it on GitHub.

  10. Shruthy says:

    Hi,

    Great done. But i want to know which part of the code should be changed for mulltilevel sub menus for the same.can u explain?

    Thanks,
    Shruthy

    • Hey, Shruthy,

      Since a couple of different people asked about adding more sub-menus, I’ve put up a third demo page that uses menus that go “three deep”:

      http://www.impressivewebs.com/demo-files/fixed-TOC-dropdown-jquery-triple-sub/

      And actually, it helped me find a couple of small bugs that were in the original. To add more depth in the sub menus, you just have to add the necessary HTML, and the style the deeper menus, and also add the necessary sections in the page that those menus will point to.

      If you see any bugs, let me know.

      • Shruthy says:

        thanks. but in the case of sub menus, when we clicking on one topic, all the list of sub menus are get displayed. if we have to click on one particular sub-topic and to display the list of that sub-topic, what we have to do?

  11. Shruthy says:

    hi,
    will u provide some examples for slidedeck with vertical slides??

    for example,look this site below:
    http://webexpedition18.com/download/slidedeck-pro/slidedeck-pro/example.html

  12. Shruthy says:

    in the example u worked, whether javascripts should be placed at the end pf the body section? or it can be placed in head section???

    • It should be at the end, which is best practice. You want your scripts loading after the page. Some scripts have to be in the head, though (like Modernizr and the HTML5 shiv), so only put your own scripts down at the end.

      • Shruthy says:

        in the example, only links are given within the page for the menus. i tried to give links to another page.its not working while clicking

        • That’s because the JavaScript disables visiting links in that menu.

          If you want a regular drop-down menu, you should use a different script. This one is specifically designed for one-page sites, not for linking external pages. So you can’t really do that with this script.

  13. Ali says:

    Very useful example. Thanks.

  14. AndrewR says:

    I really liked your code, but in my case i’d like to use it for loading other pages…

    Is it possibile, and where is the relevant code to change for achieve that?

    I have tried messing around in the doOpenItem : function () { but with no luck :-(

    • Hi, Andrew. I believe this should do what you want:

      http://jsbin.com/alekiy/1/edit

      Notice that I’ve commented out the contents of the “doScroll()” function, and changed that to visit the URL in the link instead. So all you have to do is change the HTML to have the external pages in the HREF values (instead of #section1, #section2, etc) and it should work fine.

      Only thing now is that you should probably change the name of “doScroll” to something else, since it’s not really scrolling anymore. :)

      Hope that helps.

      • AndrewR says:

        It works like a charm,
        Thank you ;-)

        Your menu could be expanded a bit, maybe implementing more levels (as I have read some comments above…) and it would be wonderful if one could make it a little user configurable (for example to choose if expand menu on mouse click or on mouseover, if menu should remain opened upon mouse leave, ecc..)

        Up to now, however, the major pitfall is that the css menu style doesn’t render correctly in IE8/9 but only in Chrome (I havent’ tested other browsers) :-(

        • Actually, I believe all except one of the issues you mentioned are already taken care of.

          The more levels thing is already implemented. You just have to add more nested lists and make a few tweaks to the CSS. See this comment above:

          http://www.impressivewebs.com/fixed-table-of-contents-drop-down-menu-jquery-plugin/#comment-21570

          Also, it’s a plugin that has an option to change the “click” event to any event you want including “mouseenter”. See near the end of the post for details on all the options.

          And another option lets you keep the submenus open when then main menu collapses.

          Probably the only missing option is to prevent it from collapsing on mouseout. I think I should add that one.

          And finally, I’ve tested in a native install of IE8 on Windows 7 and on IE10 in IE9 mode, and the styles are fine. The only thing missing is the transitions on indent.

          So I think most of what you said is already done. :)

          • AndrewR says:

            Yes you are right, the menu works fine with IE also… :-)

            I don’t know when but I changed IE to work in Non Standard (Quirks) mode from Advanced Options — F12, and it was messing all the layout…

            Now I have changed it back to IE 9 mode, and it’s ok ;-)

          • Haha… yeah, don’t even get me started on that one! I’ve done that a few times and was pulling my hair out for a few hours. :)

  15. Alan Houser says:

    Thanks for building this! I almost used it, until reading there was a problem on the iPad, so I tested it. Agreed, it works great on iPhone(5), but for some reason it just gets stuck on ipad. The menu opens on-click + you can select any link from the menu + the page scrolls to the target, but that’s all that happens. You cannot jump to another link or close the menu. A refresh is the only way to work-around it.

    • You are partly right. It gets stuck when you select a nested item. It seems to happen because of focus or something. It fixes itself if you scroll the page slightly up or down, then go back to the menu.

      Obviously that’s a problem so I’ll have to look into adding something to the script to fix that. Thanks for reminding me, I had forgotten that this was pointed out in another comment.

  16. Sandeep says:

    This is really awesome Louis! I have just one question though. I have useSubmeus: true . I am using 3 levels of ToC. However I would like the Main Menu links to work as well (similar to how the second level links work in http://www.impressivewebs.com/demo-files/fixed-TOC-dropdown-jquery-triple-sub/

    How do I go about doing that?

    • Hmm, yeah, I guess I should add an option for that, but it’s simple to fix. In the “non-customizable settings” in the JS file, you’ll see a variable defined like this:

      tocLinks : '.toc-h1 ul a',

      Change that line to read like this:

      tocLinks : '.toc-h1 ul a, .toc-h1>a',

      And that should fix it.

  17. MaskMan says:

    Nice Project Louis!

    Overall I have this working inside my website, however, I have run into an issue with the scroll.

    When using Chrome the scroll goes to the proper header and has the proper spacing, when using IE or FF the scroll goes too far, in the case of FF it sometimes has a strange bounce effect. I see this on your demo page also, not as pronounced because I had to modify the “offset.js” spacing to fit in my page (not using full the full page, just a fixed/fluid frame) but the ToC still covers the heading it is supposed to scroll to.

    Any ideas as to what is going on?

    • Yep, you’re right. Good eye on the “bounce”. I hadn’t done enough testing in the other browsers, so I didn’t even notice that. The reason it’s happening is due to the hash change, which is using the old method.

      In the JavaScript you’ll see the function called “doScroll”. In there, you’ll see this line:

      
      location.hash = s.currHash;
      

      Change it to this:

      
      if (history.pushState) {
          history.pushState(null, null, s.currHash);
      }
      else {
          location.hash = s.currHash;
      }
      

      And that will fix the “bounce” problem in all browsers that support the history API. Older browsers will get the old method in the second half of the if conditional. (Hat tip to Lea Verou: http://lea.verou.me/2011/05/change-url-hash-without-page-jump/)

      I will update the plugin when I get a chance. Thanks for the heads-up!

      Now regarding the “spacing” issues… was that the same issue as the bounce? I’m not sure if you were referring to one problem or two… ? Can you take a screenshot if it’s still problematic after the above fix? I don’t see any spacing issues in IE10 or FF.

      • MaskMan says:

        Thanks Louis!

        Looks like the bounce is fixed in FF, mixed results in IE8 (the standard here at work…) where one machine works and another does not… I will try to check in IE9 soon.

        The fix did not correct the issue with the header centering.

        Let me see if I can describe it better:
        When you click on the ToC link in Chrome the page scrolls down to the heading, but stops before the ToC box covers the heading, so you can actually read the heading. However, in FF or IE8 (will check on IE9) the ToC box covers the heading. You can fix it with the offset, however, then in Chrome the spacing is too far down the page (a lot offset) which is not a huge issue but still strange behavior.

        Thanks,

        -J

        • It looks like you were correct about that, but that was only happening before the hash/history fix. Right now, when I test the new version with the hash/history fix, FF is putting the heading in the same place as Chrome.

          The problem will occur if you are using location.hash to get the scroll position. If you are in a recent version of FF, it shouldn’t use location.hash, it should use the history API instead. The only thing I can think of that might still cause this is that you are not preventing the default behaviour (using “return false” or “preventDefault” on the click event). But even then, you would see the page jump you saw before, so I’m really not sure why you still see that.

          Do you have a demo page you can send me with your modified code? I’ll be happy to check it out if you want. Email me via my contact form if you don’t want to make the link public.

        • Oh and if you go directly to a hashed URL like this:

          http://www.impressivewebs.com/demo-files/fixed-TOC-dropdown-jquery/#section3-2

          Then that would cause the hidden heading issue to occur everywhere, including Chrome. I should probably fix that too.

        • FYI — I’ve made a bunch of updates to the code so it recognizes deep linking on page load. I’m not sure if that will fix the problems you see in IE, but I believe Firefox should be fine, unless you’re testing in a much older version.

          • MaskMan says:

            Back on project for a minute…

            Looks like FF and the hash are all good! Seems like that was my bad in reporting it was still broken.

            IE, of course, is still a bugger. Fun testing on all browsers…

            I will have an external page up next week if I don’t have other things to deal with.

  18. MaskMan says:

    OK, back on project, so here is my temp external (first developed for intranet) page:
    http://www.dot.ca.gov/dist4/rightofway/esms/lpac/lpacguide.htm

    So far the only outstanding issue with the ToC is the bounce still in IE8 and under, I have not been able to test on 9 or higher due to issues (time and IT) getting the browser installed here at work.

    The only other problem I have is that I am using a json horizontal menu in the left pane, this seems to go under the ToC container for some reason and I cannot get it to go on top. I have tried many zindex combos and another codebase to no avail. Any thoughts?

    • What is the HTML for the element that you want changed in the z-index stack? For example, I see an element with a class “top2” that has a z-index of 20, and it’s fixed position. Is that the one? It seems that’s not the one, because that one overlays the menu when it’s collapsed. If you change that one to a z-index of “1”, it fixes it. Strangely, z-index of “2” does not, which makes little sense! :)

      Anyhow, tell me the exact element you want changed, and I’ll look at it. Thanks.

      • MaskMan says:

        Top2 is a upper middle_column element that holds an image and fancy org chart link. I had to give that a zindex so it would stack on top and hide the ToC menu when I moved the ToC into its own floating block area.

        The issue I have is with one of my elements: menudiv or ul menutree1 that sit under the left_column – it is a horizontal flip out menu. Just hover over one of the left column menu items (like R/W Data Center) with the double arrow on the right, or go to another page (click ROW Home) to see what it is supposed to do.

        This is not exactly part of your code. I was just asking because this the only page I have had this issue with and was unsure if something in ToC container was causing this issue. The menudiv elements looks to be higher then the elements under middle_column but seem to flip out under that container. It is a very strange issue that so far has been above my CSS/java skill level to fix.

        • Ah I see… Here’s the problem: You have the following declared:

          
          #left_column, #right_column, .column_inner {
              overflow: hidden;
          }
          

          If you remove that, it fixes the issue. I don’t know if that property is necessary. If you’re doing that to prevent that element from collapsing because of floats, then you should use a “clearfix” method instead. See:

          http://www.impressivewebs.com/clearing-floats-why-necessary/

          Hope that helps. :)

          • MaskMan says:

            Sometimes all it takes is another set of eyes, somehow I missed that one…

            That is there to clear issues with large content from breaking the layout, if I am careful it should not cause issue with the page. I will take a look at the clearfix method just in case.

            So far the only issue I see is the footer is now scrolling over the left_column lower link page, that should be an easy fix.

            Thanks for the help and the cool ToC page!

  19. Kurieuo says:

    Does the TOC have to go at the top of the page? When I try moving it down, the menu items show above.

  20. StillLearning says:

    Hi,

    I would like to ask if this part of the script,

    
      var _gaq = _gaq || [];
      _gaq.push(['_setAccount', 'UA-1965499-1']);
      _gaq.push(['_trackPageview']);
    
      (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
      })();
    

    can be done without connecting to the server.
    Is there a downloadable version that I can locate through the local server.
    We would like to have our website to load as fast as it could be.

    Any suggestions on how I could do it?
    It would be a great help.
    Thanks in advance.

    • That’s not part of the script, that’s the stats tracker for my website, which appears on every page. You should just delete that. The stuff right above that is the stuff you need.

  21. Adrian Humphreys says:

    Nice menu, thanks.

    Unless I am missing something, however…
    I find that the entire menu will (unexpectedly) collapse to the top if:
    – any top level menu has more than five items.
    – that menu is expanded (open).
    – you open another menu item.

    You expect that expanding one menu will simply close the previously open one.

    Example structure:

    <ul id="toc" class="toc"> 
    
      <li class="toc-h1"><a href="/aboutus/home.php">Home</a> 
        <ul class="toc-sub closed">
          <li id="a"><a href="#">dummy 1</a></li>
          <li id="b"><a href="#">dummy 2</a></li>
        </ul>
      </li>
    	
    <!-- If this node is open (it has more than 5 items) ,
      opening another node will collapse the entire menu. -->
      <li class="toc-h1"><a href="test1.htm">Test</a>
        <ul class="toc-sub closed">
          <li id="1"><a href="#">Line 1</a></li>
          <li id="2"><a href="#">Line 2</a></li>
          <li id="3"><a href="#">Line 3</a></li>
          <li id="4"><a href="#">Line 4</a></li>
          <li id="5"><a href="#">Line 5</a></li>
          <li id="6"><a href="#">Line 6</a></li>
          <li id="7"><a href="#">Line 7</a></li>
        </ul>
      </li>
    	
      <li class="toc-h1"><a href="test2.htm">Test2</a>
        <ul class="toc-sub closed">
          <li><a href="#">Line 1</a></li>
          <li><a href="#">Line 2</a></li>
          <li><a href="#">Line 3</a></li>
          <li><a href="#">Line 4</a></li>
        </ul>
      </li>
    
    </ul>
    

  22. Ben says:

    How to you tell the script which sections to use?
    Do I have to manually add <section> and <h1>… tags to each part I want the plugin to show or is there any shorter way? Can’t it just parse <h1>… header tags automatically?

    • Unfortunately, it’s not that intuitive. You have to add the IDs in the markup, plus the correct HTML for the menu, to match the IDs.

      If I wrote the script today, I would probably make it generate the IDs automatically in the JS, and also generate the menu itself automatically based on the headings.

      • Ben says:

        Is there no chance you would do that? it would help me a lot for a project I need quickly, and I’m not sure how to do it myself. I would really appreciate it.

        • Haha, well, no, I can’t just do free work on request like that. I mean, it is a good suggestion for the script, but it’s not something I have time for right now. There’s a GitHub repo if you want to open an issue for that, but since that’s an enhancement, not a bug, it’s not something I can do right away.

  23. Sabbir Islam says:

    Working good but I need h1 to h6 smooth scrolling code to have any post related this?

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field and deep URLs are not allowed in the "Website" field. If you use keywords or deep URLs, your comment or URL will be removed. No foul language, please. Thank you for cooperating.

Markdown in use! Use `backticks` for inline code snippets and triple backticks at start and end for code blocks. You can also indent a code block four spaces. And no need to escape HTML, just type it correctly but make sure it's inside code delimeters (backticks or triple backticks).