Bootstrap: ScrollSpy tips…

on , most recently modified on

ScrollSpy is a handy JavaScript feature in Twitter’s Bootstrap framework. With a help of a little creative CSS and perhaps a bit more JavaScript, it is capable of facilitating beautiful scroll position activated animations.

The documentation however makes it appear extremely restrictive. As of this day, they’ve mentioned that ScrollSpy requires a Bootstrap nav component, but that’s simply not the case. In general ScrollSpy, untouched and unchanged can do a lot more than described. Usually, finding that out requires a lot of hard work, but you have me now.

Tips

Here’s a few tips and tricks you can use to better your implementation of ScrollSpy.

Attach ScrollSpy to a selector

Bootstrap docs suggests one attach the attributes data-spy="scroll" and data-target="classname" to <body> where .classname is a class attached to a nav item. It can also be done by using the following JavaScript.

$('body').scrollspy({target:'.classname'});

The structure of the nav item has to look like so.

<div class="classname">
  <ul class="nav nav-tabs">
    <li><a href="#ref1">Reference 1</a></li>
    <li><a href="#ref2">Reference 2</a></li>
    ...
    <li><a href="#refN">Reference N</a></li>
  </ul>
</div>

The .nav class in this case lists its items in the form of a dropdown as you can see in the “Note” menu on this site’s fixed toolbar.

The code in scrollspy.js contains the following line.

this.selector = (this.options.target || '') + ' .nav li > a'

So, attaching the ScrollSpy to a target makes it scout for all the anchor tags that are immediate children of list items descended from a div element with the .nav class. Usually, that’s not a problem, but it forces one to stick to that layout alone. Plus, you might want to perhaps assign additional jQuery functionality to your links. I myself have used Ariel Flesler’s ScrollTo in conjunction with ScrollSpy for that menu above. It was a bit inconvenient for my application to use the target argument.

One can easily remedy the problem by attaching ScrollSpy to a selector instead of a target. To do that one must change to the following JavaScript code.

$('body').scrollspy({selector:'.scrollclass'});

Once that’s done, assign this class to every anchor tag you wish to ScrollSpy, like so.

<li><a class="scrollclass" href="#ref1">Reference 1</a></li>
<li><a class="scrollclass" href="#ref2">Reference 2</a></li>
...
<li><a class="scrollclass" href="#refN">Reference N</a></li>

This also opens up the possibility of using .scrollclass on a control that’s not an anchor. Yes, I know you’re still required to wrap it with a list item, but that be changed with a simple tweak in scrollspy.js. I wouldn’t recommend it though. It’s much easier, cleaner and effective to style the list item tag <li> for the .active class. Besides, tweaking the script file will require you to host it yourself instead of using the many CDNs available for Bootstrap.

You needn’t worry about shifting from the target argument to the selector. The script seeks to toggle the .active class of the closest parent list item <li> tag.

Note: Doing this might cause unexpected removal of the .active class from every list item <li> in the entire page. Use it with caution.

Disable navigation

The main purpose of the dropdown, in this case, is to index all the important areas of the content and provide the ability to navigate through it. I’m all for that but, at times, that’s not really needed. One might not want a clickable index to their content, yet still allow the viewers to know what part of the content they’re on.

If you simply use Bootstrap JS and not the CSS, you could style your application the way you like. One could, for instance, design their webpage so as to hide every list item except for the one with the .active class and display a breadcrumb like index to their content in a status-bar like control.

That can be done by simply replacing the href attribute with the data-target attribute like so.

<li><a class="scrollclass" data-target="#ref1">Reference 1</a></li>
<li><a class="scrollclass" data-target="#ref2">Reference 2</a></li>
...
<li><a class="scrollclass" data-target="#refN">Reference N</a></li>

Doing this disables the anchor tag from working. However ScrollSpy continues to function as it looks for both the href and the data-target attribute.

I kind of dislike fragment identifiers in my URLs. I like keeping it clean. Having the href attribute will let you navigate but will also append the identifier to your URL in your browser’s omni-box. Instead I’ve used ScrollTo to do the scrolling for me. Bootstrap’s ScrollSpy and ScrollTo work pretty nicely together. Perhaps I’ll write a post about that sometime.

In the meanwhile you might want to check the source code of this post. The aforementioned menu is entirely created client side.

Proper offsetting

ScrollSpy is often used in sites with a fixed navbar which is at least 50px in height. If the navbar is fixed to the top, it will overlap a portion of whatever it’s spying on. It won’t activate the correct dropdown index of the element in focus while being visible. For small header tags like H5 and H6, the index activates only after it’s hidden by the navbar. It’s necessary to mention a proper offset.

In JavaScript that can be done by adding the offset argument to ScrollSpy like so.

$('body').scrollspy({selector:'.scrollclass',offset:50});

Of course, this means the element hits the navbar instead of the top of the window to trigger ScrollSpy. It’s often better to provide a bit larger offset by say 10px to 15px. An offset of 65px should be pleasant.

$('body').scrollspy({selector:'.scrollclass',offset:65});

Use with ScrollTo

There is no real need to do this as fragmented identifiers are capable of doing the same thing albeit without the cool smooth scroll. If you’re the kind who likes the aesthetics of it, well, it’s extremely easy to use.

First, include the jQuery.ScrollTo JavaScript file in your webpage. Better choose the minified version of it. Then write to the effect of the following snippet.

$('.scrollclass').click(function(){$('body').scrollTo($(this).data('target'),{duration:500,offset:-55});});

If you’ve chosen not to use the continue using the target argument instead of going for selector, you can still use ScrollTo with the following code.

$('.classname .nav li > a').click(function(){$('body').scrollTo($(this).data('target'),{duration:500,offset:-55});});

And if you’re sticking with fragmented identifiers but want that cool scrolling effect.

$('.classname .nav li > a').click(function(){$('body').scrollTo($(this).attr('href'),{duration:500,offset:-55});});

It is pretty self explanatory at this point, but using this code will provide a smooth half second long delay.

Note: The offset in ScrollTo is inverted. It should be the negative of less than or equal to the value of ScrollSpy’s offset.

That’s all

Do let me know if you’ve found any of it helpful. In a future article I’ll describe the client side script and the rationale behind the drop-down post index.

Check out this article. It handles multipage content pretty well, don’t you think?