codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

Sticky Header with Highlighting Sections on Scroll

--

Introduction

Implementing a sticky header used to be a huge pain before CSS added position: sticky. Support for some scenarios (like table headers, etc) are not supported well but you can find out more about those on the caniuse website.

We’re going to implement a sticky header that when scrolled to will automatically highlight each section as the user scrolls into them. We’ll take advantage of a few different hooks.

Setup

This will be structure we’re going to work with in. A few things to call out are the top-spacer and bottom-spacer are just arbitrary heights. This allows us to have space to scroll on top and enough room to scroll past the content section completely.

Our header is using flexbox and rendering out buttons that will be clickable to scroll to our different sections. Each of our sections will be 40vh or 40% of the view height, and we'll give each one a different color.

Our header_link renders a 3px transparent border, this is because when we apply our selected class along with it that has a 3px green bottom border it won't cause the text to jump and be out of line with the other text.

Make it Sticky

By applying position: sticky and giving it a location to sticky to will cause it to stay at that position when the top of the users screen hits that element.

The key bit is that we have placed it inside of another div. That div will wrap all of our content. This means the sticky header will be stuck until the user scrolls past the content. The content will get its height from the sections that we are rendering.

Click To Scroll

The browser also implements the ability to scroll to different elements, you can call element.scrollIntoView() or also call scrollTo on an element to scroll to a specific offset.

We are going to use the element.scrollIntoView() since we have references to all of our sections that we want to scroll into view. We define the behavior to smoothly scroll to the start position of the element.

We’ll get to it next but each section will have its own ref so we can measure the height. But we can also use those refs to the DOM elements to get their offsetTop and scroll to them.

Monitor Scroll

We need to know where the user has currently scrolled to and also be notified when scrolling happens. We can do that by attaching a scroll listener to the window. We attach it to the window because that is the what is scrolling, if there is an alternate element scrolling you could have attached an onScroll prop.

We need to always clean up our effects, and with event listeners when we call removeEventListener we need to supply the same function reference to it.

When dealing with hooks, and hooks that will depend on cleanup or hook dependencies it is best practice to create the function inside the useEffect hook.

Measuring Heights and Offsets

We’ll use the useRef hook which will allow us to get access to the DOM element of each section so we can get the height/offset. So we have a value to reference for the future we can put them in an array so when we detect something dealing with our ref we have a way to identify what thing we're talking about.

Now we attach them to our sections.

In order to know what section we’re currently in we need to get the offset top of the element as it sits on the page.

The concept being that if get the scrollY (how much the user has scrolled). Then we can see if that value is between the top/bottom of our element.

We use getBoundingClientRect() to get the height of the element. Then the offsetTop will be the top pixel position of it sitting on the page. The bottom will be the offsetTop + height of the element.

Highlight Section on Scroll

Now that we are monitoring the scroll position, and know the tops and bottoms of each of our sections we can detect if a user is looking at one and highlight it.

We’ll need 2 new pieces. The first will be a state variable so we can update and re-render when the user changes sections.

We’ll also need the reference to the header so we can measure its height and adjust our offset since the header will be sitting on top of our content and we want the bottom of the header when it hits a section to cause that section to highlight.

Attach our ref.

First we’ll get our header height, we already have a function that will get use the height or an element so we can call that and destructure. Our actual scroll position we care about will be the scrollY of the window plus the additional height of the header.

Then we can loop through each of our sections, and get the offsetTop and offsetBottom with out getDimensions function. Now we can check if the scrollPosition is greater than the top of the element. Aka the user has at least scrolled past it a little. Then we also check if the scrollPosition is less than the bottom of the element.

This logic just is checking if we are between the top and bottom. Then we check our visibleSection if it is equivalent to the section we found. We want to do this to avoid setting state on every scroll event because we've detected that the user is in the same section.

We use a find because we can bail early on finding the item we want, and this also will tell us when we aren't in a section at all.

One thing to note is that because we’re depending on the previous visibleSection we need to add it to our useEffect dependencies. When our section changes it will run our clean up, remove the window scroll listener and then re-run our effect.

Finally because we have our visibleSection state updating when we scroll we can apply our selected class to add in a green border bottom and green text to indicate to the user what section they are in.

Remove First Highlight

One other bit we need to take care of is if the user scrolls into our content, and then scrolls back to the top of the page. If we add an else if and detect when we haven’t found a selected section. That means we aren't in any section at all. Then we check if we have a section selected, and can remove our selection by setting visibleSection to undefined.

Scroll Position Restoration

If the user had scroll to a position on the page and refreshes the page, most browsers will restore the position the user was scrolled to. So in our useEffect hook if we call our scroll function it will run all the logic based on the current scroll position and update our selected state accordingly.

End

There you have it. If you just wanted a sticky header and didn’t need to know what the section you are currently looking at you could have simply added just the position: sticky. The ability to click buttons to scroll to sections, and highlight the section the user is in provides a nicer experience.

Originally published at https://codedaily.io on August 12, 2019.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

No responses yet

Write a response