
Introduction
The content of an HTML document can be very long and difficult to access only through the scroll. Because of this arduous task, developers often use internal links (page jumps) as an alternative mode of transport around the page. This useful technique has been improved with the help of Javascript to offer a better experience, primarily by offering soft jumps and then introducing the so-called Scrollspy scripts.
A Scrollspy is used to automatically update links in a navigation list based on scroll position.
Through this tutorial, we’ll be building a custom Scrollspy component. See exactly what we are going to build below:
Also, you can take a look at the working DEMO.
To accomplish this custom Scrollspy we will be using:
- Gumshoe: Simple, framework-agnostic scrollspy script.
- Smooth Scroll: Lightweight script to animate scrolling to anchor links.
- Anime.js: Flexible yet lightweight Javascript animation library.
Along with the tutorial, we’ll be explaining some features we use of these libraries, but it’s a good idea to check the Github repositories, for basic understanding.
Let’s start with the HTML structure we’ll be using, describing the key elements in the comments:
Eenie
Lorem ipsum dolor sit amet, has dico eligendi ut.
With the HTML ready, we are all set to add some style. Let’s see the key style pieces commented briefly:
h2 { &:before { display: block; content: ” “; margin-top: -110px; height: 110px; visibility: hidden; } } .page-header { position: fixed; top: 0; left: 0; width: 100%; height: 80px; background-color: #2D353F; text-align: center; z-index: 2; } .page-content { display: inline-block; margin: 80px 50px 30px; } .page-nav { display: inline-block; position: relative; margin-top: 20px; height: 40px; width: 400px; max-width: 100%; overflow: hidden; background-color: #427BAB; } nav { position: relative; width: 100%; line-height: 40px; text-align: center; background-color: rgba(0, 0, 0, 0.05); a { display: block; font-size: 18px; color: #fff; outline: none; } }
As we will be working closely with the DOM, we need to get all the elements we need first. Also, we will declare the additional variables we will be using.
var navOpen = false; var pageNav = document.querySelector(‘.page-nav’); var navEl = document.querySelector(‘.page-nav nav’); var navLinks = document.querySelectorAll(‘.page-nav nav a’); var arrowLeft = document.querySelector(‘.nav-arrow-left’); var arrowRight = document.querySelector(‘.nav-arrow-right’); var navHeight = 40; var activeIndex, activeDistance, activeItem, navAnimation, navItemsAnimation;
The following is a key part of the puzzle. This function translates the nav element to show only the selected link, using the activeIndex value.
function translateNav(item) { if (navItemsAnimation) navItemsAnimation.pause(); navItemsAnimation = anime({ targets: navEl, translateY: (item ? -activeIndex * navHeight : 0) + ‘px’, easing: ‘easeOutCubic’, duration: 500 }); updateArrows(); }
Then, we need a way to open and close the nav. The open state should let us see all the links and allow us to select one of them directly. The close state is the default one, letting see only the selected link.
function openNav() { navOpen = !navOpen; pageNav.classList.add(‘nav-open’); translateNav(); navAnimation = anime({ targets: pageNav, height: navLinks.length * navHeight + ‘px’, easing: ‘easeOutCubic’, duration: 500 }); } function closeNav() { navOpen = !navOpen; pageNav.classList.remove(‘nav-open’); translateNav(activeItem); navAnimation = anime({ targets: pageNav, height: navHeight + ‘px’, easing: ‘easeOutCubic’, duration: 500 }); }
Now let’s see how we handle the events. We need handlers to open or close the nav accordingly.
for (var i = 0; i < navLinks.length; i++) { navLinks[i].addEventListener('click', function (e) { if (navOpen) { closeNav(); } else { e.preventDefault(); e.stopPropagation(); openNav(); } }); } document.addEventListener('click', function (e) { if (navOpen) { var isClickInside = pageNav.contains(e.target); if (!isClickInside) { closeNav(); } } });
We are ready to let Gumshoe and Smooth Scroll do the magic. See how we are initializing them:
smoothScroll.init({ offset: -80 }); gumshoe.init({ callback: function (nav) { if (activeDistance !== nav.distance) { activeDistance = nav.distance; activeItem = nav.nav; activeIndex = getIndex(activeItem); if (navOpen) { closeNav(); } else { translateNav(activeItem); } } } });
Conclusion
And we are done! You can see it working here.
For the sake of clarity, we have commented only the most important parts of the code. But you can get it all from this GitHub repo.
We really hope you have enjoyed it and found it useful!