Let’s create a

Sticky ScrollSpy Table of Contents in Vue.js

Chamara Senarath
Ascentic Technology
3 min readFeb 5, 2022

--

A while ago, I was working on a documentation page, I wanted to create a Sticky Table of Contents section with the Scrolling Active States. I searched for some implementation examples on Vue, but couldn’t find what I needed. So here’s a little guide that I hope it’s useful for you to implement on your Vue apps.

Creating our Vue App

There are several ways to install Vue, however, installing Vue CLI is the simplest. You can install Vue CLI globally by running the command below:

npm install -g @vue/cli

Once installation is complete, you can create a new project with the following command:

vue create sticky-scrollspy-toc

Let’s replace App.vue file with the following code segments.

1. The Markup

HTML

In the code above, we have created two sections. The first <div> is for the page content and the second <section> inside the <nav> is for the table of contents. It is required to have id attributes for each section so that, we can use them to scroll into the corresponding section. href attribute in each list item is used to navigate to section ids.

2. Sprinkle some CSS on top

CSS

The code above,position: sticky; is used to make the navigation stay in place as you scroll. It is important to add align-self: start; to <nav>. If we would omit it, the <nav> element would be as high as the enclosing <main> element.

3. ScrollSpy with IntersectionObserver

According to the MDN docs:

“The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.”

Let’s use the IntersectionObserver in our app, in order to highlight the links in the table of contents based on the page position.

Let’s break down the script:

  1. We added a reference to the observer in the component’s data. This is important since we will be using this reference in a few places.
  2. We initialized the observer in the created hook. Notice that we use this.$el as the observer’s root. You can adjust the threshold to match your page content.
  3. In mounted hook, we use query selector to catch all the <section> tags that have an id attribute. Using the observer reference, we make each section observed.
  4. We added the onElementObserved method. In the method, we get all the entries from the observer and we extract target and isIntersecting from each entry. In the for loop, we get the id from the intersecting section and if the section is intersecting, we add active class to the relevant link in the table of contents. Otherwise, we remove active class.
  5. Finally, we disconnect the observer in beforeDestroy hook to ensure the browser is not computing the intersection after the current page gets destroyed.

You can see the final result in CodeSandbox. Feel free to play with it and try it for yourself. 💻

--

--