Improving navigation using Scrollspy in Bootstrap

Now that we have fixated the navbar at the top of the page, it will no longer disappear as the user scrolls down the page. However, now we are faced with a new problem. As the user clicks on a navbar item, they have no way of telling which section of the site they are visiting, or whether the section has loaded successfully, without actually examining the section’s content (refer to the screenshot in figure 4.2). Wouldn’t it be nice if the user could tell which section they are currently visiting by looking at the navbar? To this end, we will need to find a way of highlighting the navbar items based on the user’s navigation. For example, if the user is browsing the Services section, then we will need to update the color of the Services navbar item:

Figure 4.2: Currently, all navbar items are the same color; there is no way for the user to tell where on the page they currently are just by looking at the navbar itself

Luckily, Bootstrap comes equipped with a plugin that allows us to automatically update the navbar items based on the user’s navigation: meet Scrollspy. Scrollspy allows us to automatically update navigation targets based on the user’s current position within the page. In other words, Scrollspy allows us to denote scrollable areas within our page and update elements to denote when the user enters a specific scrollable area. It also allows us to add scroll animations to our page.

Scrollspy is already included in our installed Bootstrap build, so there is no need for any additional downloads. The first thing that we must do is denote our scrollable area.

By adding data-spy=”scroll” to the element that contains our contents, we are, in essence, telling Bootstrap to use anything contained within the given element as our scrollable area. Typically, data-spy=”scroll” is added to the body element of our page:

<body data-spy=”scroll”>

</body>

The data-spy attribute is by far the most important attribute when using Scrollspy, as without it, Bootstrap wouldn’t know what to do with the remainder of the Scrollspy instructions. It is also important to note that Scrollspy will only work with container elements whose position is relative.

Next, we must tell Scrollspy how to identify our navbar element. After all, how else will the plugin know where the navbar items that it has to update are located? It is important that you point to the parent element that contains the list of navbar items. If you point to any other element, the feature will not work. Before we can tell Scrollspy about our list of nav items, we must first be able to uniquely identify them. To do so, we will use our navbar’s class name, navbar. Alternatively, we can assign our navbar an id by adding id=”navbar” to our navbar element. We will then make Scrollspy aware of the navbar using the data-target attribute:

<body data-spy=”scroll” data-target=”.navbar”>

<nav class=”navbar navbar-expand-lg navbar-myphoto fixed-top”>

<a class=”navbar-brand” href=”#”>MyPhoto</a>

<button class=”navbar-toggler” type=”button” data-toggle=”collapse”

data-target=”#navigation”>

<span class=”navbar-toggler-icon”></span>

</button>

<div class=”collapse navbar-collapse” id=”navigation”>

<ul class=”nav navbar-nav mr-auto”>

<ul/>

</div>

</nav>

</body>

Note that when using the data-target attribute, class names must be preceded by a . (period), while each id must be preceded by # (hash). Since the active class automatically gets applied to selected menu (link) elements, we should add some custom styles to adequately highlight the active menu item:

.navbar-myphoto .nav-item > .active {

color: gray !important;

text-decoration: underline;

}

Now save and refresh, and then try to navigate between sections and observe the navbar’s behavior. Items are automatically highlighted (refer to figure 4.3) as you change position. Take a look at the following screenshot:

Figure 4.3: Navbar items are highlighted depending on the user’s current position within the page

As an alternative to the navbar-nav list element, we can also style our menu items as pills, using the nav-pills class. Scrollspy will also work, applying the active class to the nav- link elements. At times, this may look nicer (take a look at figure 4.4):

<ul class=”navbar-nav nav-pills mr-auto”>

<li class=”nav-item”>

<a class=”nav-link active” href=”#welcome”>Welcome</a>

</li>

<li class=”nav-item”>

<a class=”nav-link” href=”#services”>Services</a>

</li>

<li class=”nav-item”>

<a class=”nav-link” href=”#gallery”>Gallery</a>

</li>

<li class=”nav-item”>

<a class=”nav-link” href=”#about”> About</a>

</li>

<li class=”nav-item”>

<a class=”nav-link” href=”#contact”> Contact Us</a>

</li>

</ul>

Figure 4.4: Navbar pill items are highlighted, depending on the user’s current position within the page.

Customizing scroll speed

Great! Our navbar items are now automatically updated based on the user’s scroll position. Easy, huh? However, we’re not quite done yet. The transition between sections actually feels quite rough. The page literally jumps from one section to another. Such a jerky movement is not very pleasing. Instead, we should improve the user experience of our website by making this transition between sections smoother. We can quite easily accomplish this by customizing the scroll speed of our page using jQuery. The first step in this task involves automating the scroll—that is, forcing a scroll event to a target on the page using jQuery, without the user needing to actually perform a scroll operation. The second step involves defining the speed of such a scroll operation.

As it turns out, the developers behind jQuery already thought about both of these steps by providing us with the animate method. As its name implies, this method allows us to apply an animation to a given set of HTML elements. Furthermore, we can specify the duration of this animation (or use jQuery’s default value). If you take a look at the jQuery documentation for animate (http://api.jquery.com/animate/), you will see that one of the possible property in the properties parameters is scrollTop. Therefore, by writing {scrollTop: target}, we can automatically scroll to target (the target being the target location of the scroll).

Now, before we can apply our animation, we must ask ourselves on which element the animation should take effect. Well, the nesting of our HTML document takes the form of the following elements:

  • body
    • #welcome
    • #services
    • #gallery
    • #about
    • #contact

Furthermore, the scroll serves to navigate between elements. Therefore, it makes sense to apply the animation to the parent element of our HTML document:

$(‘html,body’).animate({

scrollTop: target

});

Great! So, we have told our browser to apply a scroll animation to the given target, but what exactly is the target? How do we define it? Well, our navbar items are anchor tags, and the anchor’s href denotes the section to which an internal scroll should apply.

Therefore, we will need to access the individual navbar’s href target and then use this as the target for our scroll animation. Luckily, this is also easy to do using jQuery. We first need to add an event listener to each navbar item. Then, for each clicked anchor, we extract the element to which its href attribute refers. We then determine this element’s offset and pass this offset to our scroll animation.

To add an event listener to an element, we use jQuery’s on method and pass click as a parameter to denote that we want to capture a click event:

$(“nav div ul li a”).on(‘click’, function(evt) {

});

Note how our selector only identifies anchor tags that are located within an unordered list inside a div. This ensures that only navigation menu items are being matched, as our markup should not contain any other anchor tags that are located inside list items belonging to a navigation element. Alternatively, we can just as easily use the .nav- link selector, which is as follows:

$(“.nav-link”).on(‘click’, function(evt) {

});

From within our event listener, we can access the object on which the click was performed using the this keyword. Thus, our clicked object will always be an anchor instance, and we can access the contents of its href. Specifically, we can access the string that follows the hash symbol within the href. To do this, all we have to write is $(this).prop (hash) or (better and more concise) this.hash.

Remember that the string following the hash within an href identifies an internal element within the HTML document. We can, therefore, use the extracted string as a jQuery selector to get jQuery to retrieve the desired instance of the HTML element. All that we need to do then is use jQuery’s offset() method to calculate our element’s coordinates for us:

$(“.nav-link”).on(‘click’, function(evt) {

var offset = $(this.hash).offset();

});

Voila! We are almost done! Let’s piece it all together, wrap the code into a script tag, and place the code at the bottom of our HTML document:

<script type=”text/javascript”>

$(document).ready(function() {

$(“.nav-link”).on(‘click’, function(evt) {

var offset = $(this.hash).offset();

$(‘html,body’).animate({ scrollTop: offset.top });

});

});

</script>

Save the document and try it out in your browser (example03.html). Our code executes correctly, but something still isn’t quite right. Did you note the odd flicker as you clicked on the navbar items? This is because the anchor tag on which we are clicking still tells the browser to jump to the specified internal element. At the same time, we also instruct our browser to animate a scroll to the element. To resolve this duplication, we have to prevent the on-click event from trickling down to the anchor tag once it reaches our event listener. To do this, we call preventDefault on the event:

$(“.nav-link”).on(‘click’, function(evt) {

evt.preventDefault();

//…

});

Apply the changes, save, refresh, and try again. Great! Our custom scroll works, but there is one last annoyance. Clicking on the drop-down menu that launches our Profile and Settings modal dialogs. Did you note how their anchor tags link to a plain hash symbol?

Let’s deal with this corner case by checking whether jQuery’s offset() method can successfully execute an offset. To do this, we must wrap our call to animate within an if statement so that our final block of code is as follows:

<script type=”text/javascript” >

$(document).ready(function() {

$(“.nav-link”).on(‘click’, function(evt) {

evt.preventDefault(); var offset = $(this.hash).offset();

if (offset) {

$(‘body’).animate({

scrollTop: offset.top

});

}

});

});

</script>

Source: Jakobus Benjamin, Marah Jason (2018), Mastering Bootstrap 4: Master the latest version of Bootstrap 4 to build highly customized responsive web apps, Packt Publishing; 2nd Revised edition.

Leave a Reply

Your email address will not be published. Required fields are marked *