Scrollspy using Intersection Observer

I recently had to make a Scrollspy for my personal web portfolio. I wanted to use Intersection Observer as it is widely supported now. Unfortunately, it isn’t very simple and there isn’t much on the internet on how to make it work. So, I decided to write a small article to help out others looking for the same.

Photo by Benjamin Elliott on Unsplash

Scrollspy is a navigation mechanism used by Bootstrap. As per the docs,


Automatically update Bootstrap navigation or list group components based on scroll position to indicate which link is currently active in the viewport.

Basically, it is a piece of JavaScript that allows your headers and lists to know which element is currently on the screen. This is usually used to highlight the active section in the navigation bar.

An example of Scrollspy

Intersection Observer

Earlier, the effect was achieved using a scroll event listener which would check all the sections one by one on every scroll and then update the section currently in the viewport to be active. Luckily , we have the new IntersectionObserver API which makes the task extremely simpler and more efficient. If you don’t know about the IntersectionObserver API, check out this excellent article.

The technique

Here we will do the following:

  1. Keep the threshold to 0 (the default value). That means whenever the first pixel of any section enters the viewport or the last pixel of any section leaves the root, the callback will be executed.
  2. Intersection root is the entrie viewport by default. We want the intersection to be observed at horizontal line. We can do that by adding a margin to the root and reducing the observed area to a horizontal line. To set it exactly middle of the screen, set rootMargin to -50% 0px
Scrollspy using Intersection Observer

Code is pretty straightforward:

window.onload = () => {
const sections = document.getElementsByTagName("section");
const name = document.getElementById("section-name");
for (let i = 0; i < sections.length; i++) {
const observer = new IntersectionObserver((entry) => {
if (entry[0].isIntersecting)
name.innerHTML = sections[i].innerText;
rootMargin: "-50% 0px"

You can see it in action here:

Looking for job opportunities. CSE Undergrad @NIT Bhopal | Analyst Intern @ American Express | All things Web

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store