How to create a sticky table of contents with a limit of elements up and down based on active item

To consider I know how to make a sticky table of contents. On which I need help is how to display the say 5 items up and 5 item down next to the current active item and that scrolling up or down updates accordingly active and up and down items.

Example of base TOC

So if I have scrolled to " City Detail" it should only 5 items above it:
-Request & Response
-Cities Overview
then it wil go " City Detail"
and below the other 5 items
-City Config
-City Spots Overview
-City Spot Detail
-City Icons Overview
-City Icon Detail

Meaning all the other elements should be hidden at that scroll position.

If you want these nav links to show only a max of 11 (5 above, + 1 active + 5 below), you could continue through the use of intersection observer + css styles.

To give you an idea, you could assign certain classes to 5 above and 5 below you currently intersected element.

By the way, good use of intersection observer! First time I’ve seen it used directly (without an external library).


Well I honestly cant take credit for it, it was posted on CSSTricks, like it too :slight_smile:

Thank you so much!! and good idea!! Perhaps with previous element sibling and next element sibling I guess? or maybe a better way?

You could use indexing but I know that intersectionObserver has an entries array and the entry has a bunch of properties that may be of use and give the ones not within your range a display of none (when not near the intersecting element)

This might be helpful

1 Like

Thank you!!! I was about to ask for a reference/example!! but wanted first to finish looking / googling

Edit: I belive this are properties from MDN:

    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.time

Should I use intersectionRatio? as it is here?

Edit I will just stick to editing the reference you posted I’m on it…

I dont know honestly how to count 5 item with intersection obsever, so I will resort to previous/next siblings i guess, is like it only takes into account the area…
Also sometimes more than one item in the table of contents is active, i would like to restrict that to only one…

you could get the array of all intersection records then filter out the array so you get items closes to your specified index.

More than one item is active because multiple items are intersecting. Right now it is checking what is in your viewport as that’s the rootMargin by default.

You can use the rootMargin when initially setting up Intersection Observer to essentially say what part of the viewport you want to consider intersecting.

MDN has good docs on it

Intersecting ratio may not be ideal IF the section is greater than the viewport (you’ll won’t intersect). You’ll need to know these details ahead of time since you’re putting the intersection on the section element which has a bunch of padding applied. In the IntersectionObserverEntry you can see the intersectionRatio. You wrote that if it is greater than 0, you give the active class.

You could put the observer on the headings itself but then that can also be problematic if you’re not on that section (if you are on Authentication, and scroll up it’s not going to intersect until you get to the Request heading).

1 Like

Thank you!!!
I found a pen that has only one item at a time with intersection observer. Problem is I don’t fully grasp it, it has an additional conditional for an entry intersecting… relevant code below:

if (entry.isIntersecting) {
    if (currentY > previousY && index !== 0) {
      console.log(id + ":1 enter top");
    } else {
      console.log(id + ":2 enter bottom");
  } else {
    if (currentY > previousY) {
      console.log(id + ":3 leave bottom");
      const lastLink = document.querySelector(`#${headingIds[index - 1]}-link`);
    } else {
      console.log(id + ":4 leave top");

And complete source here:

Also ideally I would like to replicate MDN’s " In this article" behavior

These two observers (codepen vs mozilla) are implemented differently.

If you notice, in the codepen, it is making the last intersecting element the active one. The if condition is checking the y values on the entry

Mozilla’s is different, you’ll notice that if you go to a heading, then scroll up a little, it will change the active section even though that heading isn’t visible in the viewport.

I see then I should go for Mozilla’s one, but just in case I explain: of all the intersecting elements I intend to only select the top most that’s the idea at least
btw sorry for the delay… and thanks

I think I can just create an array and set only the first element to add active status of all the intersecting elements.
Edit found this using this aproach javascript - IntersectionObserver with multiple isIntersecting headers, not working correctly - Stack Overflow
but maybe theres a better and/or simpler way?

Ok I’m officially lost, I don’t know how to select only the top most of all the intersecting elements in JavaScript… If I use an array it only works when scrolling down, but scrolling up messes the order. Help is appreciated…

Update: this works but selects the last element instead of the top most/first

      if (entry.isIntersecting) {
      document.querySelectorAll(".text-danger").forEach((z) => {