Understanding Bootstrap’s Affix and ScrollSpy plugins
One of the things you probably have noticed when you visit Bootstrap’s website is the right-side navigation that moves as you scroll through the page. This scrolling effect can be achieved combining the Affix and ScrollSpy jQuery plugins, both available as Bootstrap components.
In this article, I’ll show you how to add this cool feature to one of your own projects. As always, to better demonstrate these, I’ll use a demo project.
Let’s start by taking a look at the HTML structure of it.
The Basic Markup for Our Demo
For small screens we use a single column layout. For medium and large screens our layout will consist of two columns. On the one hand, we have the navigation, which occupies one quarter of a row’s width. On the other hand, we have the main content sections, which occupy the other three quarters of a row’s width. Each of these sections has an id
attribute value that matches the href
attribute value of the corresponding anchor (a
) element.
In our example, we want the scrolling effect to occur only when the viewport exceeds 991px (i.e. medium and large screens). For this reason, we add the hidden-xs
and hidden-sm
helper classes to the navigation.
See the code below. Note that for brevity, we’re not including content that isn’t relevant to the technique.
<div class="col-md-3 scrollspy">
<ul id="nav" class="nav hidden-xs hidden-sm" data-spy="affix">
<li>
<a href="#web-design">Web Design</a>
</li>
<li>
<a href="#web-development">Web Development</a>
<ul class="nav">
<li>
<a href="#ruby">
<span class="fa fa-angle-double-right"></span>Ruby
</a>
</li>
<li>
<a href="#python">
<span class="fa fa-angle-double-right"></span>Python
</a>
</li>
</ul><!--end of sub navigation-->
</li>
</ul><!-- end of main navigation -->
</div>
<div class="col-md-9">
<section id="web-design">
</section>
<section id="web-development">
<section id="ruby">
</section>
<section id="python">
</section>
</section>
</div>
Now that we understood the basic structure of our project, we can include the plugins to add the functionality.
Using Affix
The Affix plugin will help us “fix” the position of our navigation section, while allowing us to add vertical offsets to this fixed element, depending on where the user has scrolled.
To use the Affix plugin in our project, we have to specify the element that will receive the “affix” behavior. We can do this by adding the data-spy="affix"
attribute/value to it. In our example, the desired element is the ul
element.
The plugin toggles between three classes, described here:
- The
affix-top
class, which indicates that the element is in its top-most position. - The
affix
class, which is added when the element starts to scroll off the screen, and which applies theposition: fixed
property to it. - The
affix-bottom
class, which indicates thebottom
offset of the element.
In brief, the plugin changes the element’s type of positioning as the user scrolls up and down the page, using the three classes to do so.
Note that we can optionally take advantage of all the classes in our implementation. The most important class is the affix
one, which allows us to pin an element as the user scrolls down the page. However, depending on the structure of our project, we might want to use the affix-top
and/or affix-bottom
classes as well.
Let’s now see how we can include all three classes in our example.
First, we assign the affix-top
class to the ul
element. To do this, we can use either custom data-* attributes or JavaScript. Here’s the required jQuery/JavaScript:
$('#nav').affix({
offset: {
top: $('#nav').offset().top
}
});
At this point, we have applied the element’s top
position. This is around 390px. By default, its position is set to static
. Even though Bootstrap mentions that no CSS positioning is required, I’ve set its position to relative
. Here’s a screenshot with annotations to show what’s going on:
The HTML of the ul
element looks like this when the page first loads:
As you can see, when the user first starts to scroll, the ul
element has the affix-top
class. When the “scrolling” exceeds the element’s initial top
position (around 390px), the ul
receives the affix
class and its position changes to fixed
. Then we set its new top
(try to change its value to see the difference) position and width
properties. Here are the corresponding styles:
.affix {
top: 20px;
width: 213px;
}
@media (min-width: 1200px) {
.affix {
width: 263px;
}
}
Below is a screenshot that corresponds to this phase:
And the generated HTML:
As the user scrolls, the ul
is pinned to the top of the page. Of course, this is a nice effect, but we eventually want to stop the pinning. We can do this by using the affix-bottom
class. To cause the plugin to trigger this class, we have to specify the bottom
offset of the target element. Again, this can be achieved by using either custom attributes or jQuery/JavaScript. Here’s how to do it with jQuery:
$('#nav').affix({
offset: {
bottom: ($('footer').outerHeight(true) +
$('.application').outerHeight(true)) +
40
}
});
During this phase, we have to use CSS to specify the type of positioning for our element. For instance, we can apply position: absolute
or position: relative
to it. In our example, I’ve chosen the first option. We also set its width
property. Here are the required styles:
.affix-bottom {
position: absolute;
width: 213px;
}
@media (min-width: 1200px) {
.affix-bottom {
width: 263px;
}
}
At this final stage, our demo will look like this:
Note: This final screenshot is based on a typical desktop screen resolution.
And here is the generated HTML:
As shown above, based on the bottom
offset that we defined with jQuery, the plugin applies a new top
offset to the element.
Finally, it’s important to mention that in the case of this specific demo, we had to specify a bottom
offset for the element. This is because there’s content after the education-related sections. For instance, if we didn’t specify an offset, our page would look something like this:
Now that we have our Affix plugin working, we can add the ScrollSpy functionality.
Using ScrollSpy
To add ScrollSpy to our project, we have to define the element we want to “spy” on during page scrolling. Usually this will be the body
element. ScrollSpy also requires that we use a Bootstrap nav component.
First, we apply the data-spy="scroll"
attribute/value to the “spied” body
element. At this point, it’s worth mentioning that Bootstrap suggests setting the position of our “spied” element to relative
.
Next, we identify the specific parts of that element we want to track by adding the data-target
attribute to the element we’re spying on (again, in our case this is the body
). The value of this attribute should match the id
or the class
of the parent of the nav component. So the body
tag would look like this (notice the “.” included as part of the value of the data-*
attribute):
<body data-spy="scroll" data-target=".scrollspy">
Similar to the Affix plugin, there’s also the option to activate the ScrollSpy plugin via JavaScript. In our example, we won’t use this option.
After activating the plugin, our HTML would look like this:
<body data-spy="scroll" data-target=".scrollspy">
<!-- content here... -->
<div class="col-md-3 scrollspy">
<ul id="nav"
class="nav hidden-xs hidden-sm"
data-spy="affix">
<!-- nav items here... -->
</ul>
</div>
<!-- content here... -->
</body>
And the only necessary CSS:
body {
position: relative;
}
Notice that we “watch” the direct parent element of the ul
and not the ul
.
We can use our browser’s developer tools to understand what the plugin does. While the user is scrolling, the plugin “watches” the target element and adds the active
class to any appropriate li
child elements. We then style the active elements based on this class. Some of our li
elements contain sub navigation elements, which we can also style to see a nice effect.
The screenshot below shows the generated HTML when the ScrollSpy plugin is in use:
Below are the styles I’ve applied to the nav component in our demo. Notice the styles take into account that I’m using nested nav components.
.nav .active {
font-weight: bold;
background: #72bcd4;
}
.nav .nav {
display: none;
}
.nav .active .nav {
display: block;
}
.nav .nav a {
font-weight: normal;
font-size: .85em;
}
.nav .nav span {
margin: 0 5px 0 2px;
}
.nav .nav .active a,
.nav .nav .active:hover a,
.nav .nav .active:focus a {
font-weight: bold;
padding-left: 30px;
border-left: 5px solid black;
}
.nav .nav .active span,
.nav .nav .active:hover span,
.nav .nav .active:focus span {
display: none;
}
Conclusion
Here’s the link to the final demo:
Bootstrap Affix and ScrollSpy Demo
In this article, we’ve seen how to build scrolling effects using Bootstrap’s Affix and ScrollSpy plugins. You’ve probably seen these components in action on Bootstrap’s documentation pages, and now you know how they work.
If you’ve used these components or something similar from a third-party, feel free to let us know about your experiences in the discussion.