Scrollspy using Intersection Observer

Photo by Benjamin Elliott on Unsplash
An example of Scrollspy

Intersection Observer

Parameters and options

  1. Intersection root: The region which is to be monitored for intersection with specified nodes. It defaults to the viewport but can also be set to a DOM node.
  2. Root margin: By default, entire root is monitored. But we can specify margins around the root to monitor a larger (or smaller if margins are negative) region than the root.
  3. Threshold: A value between 0 and 1 which specified how much part of the target element(s) should be in the intersection root to trigger the callback function. 0 means as soon as even one pixel enters the region or as soon as the entire node leaves the region. 1 means that the entire node should be in the region. Multiple values can be provided in form of an array.
  4. Callback: A function to be called whenever an intersection takes places.
let callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.time

The technique

  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 entire 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
  3. Get a list of all the sections in the page and connect them to the intersection observer.
  4. Whenever an intersection occurs, find out which element caused the intersection. To do that, use entry.isIntersecting property.
Scrollspy using Intersection Observer
window.onload = () => {
const sections = document.getElementsByTagName("section");
const label = document.getElementById("section-name");

const observer = new IntersectionObserver((entries) => {
for(const entry of entries)
label.innerHTML =;
rootMargin: "-50% 0px"
for (let i = 0; i < sections.length; i++)




Frontend engineer at GrowthDay | All things Web

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Using Webpack with ArangoDB and Foxx

Understanding this.setState({ [name]: value})

Understanding JavaScript Memory Management using Garbage Collection

ASCII Graph Generator - Blazor WebAssembly Demo

ASCII graph generator Blazor WebAssembly demo user interface

Upgrading an AngularJS Project to Angular

In node.js, always query in JSON from PostgreSQL

How to Understand GaaS as a Business Model?

Two people are playing games on PC.

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
Yash Mahalwal

Yash Mahalwal

Frontend engineer at GrowthDay | All things Web

More from Medium

Running containers in Openshift with custom SELinux type

Analyze and Optimize Webpack Bundles Size and Contents


Introducing Surfboard