Sticky Navigation With ScrollSpy Using Intersection Observer API

Category: Javascript , Menu & Navigation | January 13, 2020
Views Total:4,001 views
Official Page:Go to website
Last Update:January 13, 2020


Sticky Navigation With ScrollSpy Using Intersection Observer API


This is a sticky on-page navigation (a.k.a table of contents) that automatically highlights the active menu items based on the visibility of their corresponding content sections within the document.

Users are also able to switch between content sections with a smooth scroll effect by clicking the items in the navigation. Heavily based on the Intersection Observer API.

How to use it:

1. Create a list of nav links pointing to the page sections within the document.

<nav class="section-nav">
    <li><a href="#section-1">Section 1</a></li>
    <li><a href="#section-2">Section 2</a></li>
        <li class=""><a href="#section-2-1">Section 2-1</a></li>
        <li class=""><a href="#section-2-2">Section 2-2</a></li>
        <li class=""><a href="#section-2-3">Section 2-3</a></li>
    <li><a href="#section-3">Section 3</a></li>
<section id="section-1">
  Section 1
<section id="section-2">
  <section id="section-2-1">
    Section 2-1
  <section id="section-2-2">
    Section 2-2
  <section id="section-2-3">
    Section 2-3
<section id="section-3">
  Section 3

2. Enable the smooth scrolling effect.

html {
  scroll-behavior: smooth;

3. Make the navigation sticky.

nav {
  position: sticky;
  top: 2rem;
  align-self: start;

4. Style the sticky navigation.

.section-nav {
  padding-left: 0;
  border-left: 1px solid #efefef;
.section-nav a {
  text-decoration: none;
  display: block;
  padding: .125rem 0;
  color: #ccc;
  transition: all 50ms ease-in-out; 
.section-nav a:hover,
.section-nav a:focus {
  color: #666;

5. Highlight the active menu items.

.section-nav > a {
  color: #333;
  font-weight: 500;

6. The main script to enable the scrollspy.

window.addEventListener('DOMContentLoaded', () => {
	const observer = new IntersectionObserver(entries => {
		entries.forEach(entry => {
			const id ='id');
			if (entry.intersectionRatio > 0) {
				document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.add('active');
			} else {
				document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.remove('active');
	// Track all sections that have an `id` applied
	document.querySelectorAll('section[id]').forEach((section) => {

You Might Be Interested In:

Leave a Reply