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.
Scrollspy is a navigation mechanism used by Bootstrap. As per the docs,
Scrollspy
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.

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:
- Keep the
threshold
to0
(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. - 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

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"
}); observer.observe(sections[i]);
}};
You can see it in action here: http://intersection-scrollspy.surge.sh/