MTJ Hax

Twitter Bootstrap’s Scrollspy Plugin Needs Better Docs

Posted in Uncategorized by mtjhax on February 11, 2013

UPDATE April 2018: At some point, my non-working example started working (in Chrome at least). Since I pinned the version of Bootstrap I was using, it seems likely that Chrome made some changes in DOM or JavaScript behavior. Leaving this here to document what I assume is still the correct way to implement Scrollspy.

I was trying to get Twitter Bootstrap’s Scrollspy plugin working so I can have a nice sidebar-nav menu like theirs that highlights automatically as you scroll the page. I created a JSFiddle here to demonstrate what I am trying to do.

Click here to see the non-working example full-screen

I thought I followed their instructions to the letter. The body tag contains attributes data-spy="scroll" to activate scrollspy and data-target="#my-nav" to indicate which nav I want to highlight to match the scrolled page. After some trial-and-error I removed the data-target attribute and presto:

Click here to see the working example full-screen

This seemed to contradict their documentation. Some digging uncovered this issue report on the Bootstrap GitHub page.

As it turns out, Bootstrap assumes that your nav menu will have a wrapper of some sort, like a div, and that the data-target attribute needs to reference the nav wrapper, not the nav itself:

    
    <!-- WRONG -->
    <div>
      <ul id="my-nav" class="nav nav-list affix">
    
    <!-- CORRECT -->
    <div id="my-nav">
      <ul class="nav nav-list affix">

Click here to see the corrected example full-screen

Apparently, my original example worked when I removed the data-target attribute because it defaults to finding the first nav in the body. The instructions on how to activate scrollspy with JavaScript are very misleading. They suggest:

$('#navbar').scrollspy();

This implies that you should call the scrollspy() method on the nav object, but if you do it, it results in the behavior where the last entry in your nav menu is activated and the menu will not change as you scroll. I thought you needed to target the wrapper, as with the HTML attribute method, but this also does not work. If you use $(document.body).scrollspy() it seems to work, but again this seems to be defaulting to use the first nav it finds in the page.

After looking at the Bootstrap source code, it’s became obvious. To initialize ScrollSpy with JavaScript, the scrollspy() method should not be called on the target nav, but on the container being scrolled. The target nav can be specified as an attribute similar to the way the offset is specified. Example:

// using the not-explicitly-documented 'target' attribute
$('body').scrollspy({ target: '#my-nav' });

Another JSFiddle to prove that it works.

18 Responses

Subscribe to comments with RSS.

  1. David Taiaroa said, on March 16, 2013 at 12:59 pm

    Thanks for this. You’ve explained behaviour that I was seeing, but didn’t know why!

  2. Jonas Olsen (@jonasdo) said, on March 20, 2013 at 1:39 pm

    Thank you so much. You just made my day!! Bootstrap documentation sucks

    • mtjhax said, on March 20, 2013 at 3:35 pm

      Well, “sucks” is a bit harsh. I might say it is just a bit terse. Surprised there aren’t more introductory guides to using bootstrap.js online.

  3. Kieran said, on April 30, 2013 at 10:14 am

    Good man, exactly what I was after. Didn’t want the attributes on the body tag, but the examples they give do not show how to do this properly. Thanks.

  4. Dario said, on May 17, 2013 at 9:40 pm

    Just had this problem and fixed it all up. There is a lot of stuff lacking in the documentation. One other thing they do not talk about is the scroll box resizing because as your scroll it leaves the span. So what they did behind the scenes was give each box a set width.

    Lots of little undocumented tricks!

  5. Christoph Rohrer (@chris_rohrer) said, on June 26, 2013 at 8:57 am

    thanks a lot for sharing this – I found out that my problem was that scrollspy looks for the class “nav”. Reading the source is a good idea^^

  6. Alex Leonard said, on July 29, 2013 at 9:26 am

    Thanks for the clarifications! Much appreciated :)

  7. Mike Boardley said, on August 18, 2013 at 12:12 am

    This helped me out a ton as well! – THANK YOU. :)

  8. Anon said, on August 20, 2013 at 3:06 am

    Additional things I needed: the comments with the # broke my code (use // instead).
    You need the javascript OR data-attribute method, not both.
    I used the javascript method for scrollspy and the data attributes data-spy=”affix”, data-offset-top=””, data-offset-bottom=”” to get the affix working.
    If you’re affix-bottom is flickering, it needs a position:relative on one of its parent divs.
    You need to set the .affix’s top and the .affix-bottom bottom on your own, it is not preset.

    • mtjhax said, on August 21, 2013 at 12:57 am

      Putting Ruby comments in JavaScript and HTML examples… facepalm. Fixed. Thanks for the affix tips!

  9. Hudson said, on August 21, 2013 at 5:38 am

    Hey man, thanks a lot!!
    I was almost giving up haha.

    Hugs from Brazil!

  10. Lowwa said, on August 28, 2013 at 2:45 pm

    You saved my life Sir ! I had a very strange behaviour with my nav, items were twinkling between active and non-active state, when I was using the scroll.

    I first thought it was due to a bad value of the ‘offset’ JavaScript option : it was the only parameter described in the doc !

    Thanksfully I found your article and tried to use the ‘target’ option… Now it works like a charm !

  11. Jen said, on September 27, 2013 at 8:23 pm

    Thank goodness for this post! I was totally confused as to what I was doing wrong! My main problem was that I had the class I was spying on , on the nav element, not the wrapper. Once I moved things it worked. PHEW! thanks!!!

  12. Anthony Broadbent said, on October 1, 2013 at 3:24 pm

    I had exactly the same issue and to fix it all I did was correctly declare my doctype “” and it was fixed. I added your code and it still seemed to only select the bottom item. Then when I fixed my DTD it worked so I then removed your JS code and it still worked. I think the reason why your JSFiddle works is because the DTD is already declared for you.
    Just my contribution.
    Thanks
    Ant

    • mtjhax said, on October 1, 2013 at 4:13 pm

      I didn’t fix my issue with JavaScript. I fixed it by moving id="my-nav" from the ul tag to the wrapper div, so the data-target attribute referenced the correct element. This link is an example of a JSFiddle with no JavaScript that causes the problem, and this one is an example of the problem resolved by using the proper data-target. My JavaScript examples were just to show how the Bootstrap docs were a little misleading and missing an explicit description of the target attribute. However, it is very interesting to know that an incorrect doctype can cause similar problems.

  13. Mizzinc said, on November 11, 2013 at 7:20 am

    Finally, a fantastic write up on this. Thank you!

  14. Ryan said, on January 25, 2014 at 4:23 pm

    This was very helpful for figuring out Bootstrap scrollspy affix issues. I notice you add the class “affix” to the unordered list. I’m pretty sure adding data-spy=”affix” to the direct parent to the UL will apply all three affix classes appropriately. That’s what I’ve concluded. They need a layman who knows Bootstrap inside out to write their docs :) Thanks for this, you made me find my mistake


Leave a comment