Smooth-Scrolling Anchor Menu in ReactJS

Kenneth Ahlstrom
The Coder’s Guide to Javascript
3 min readApr 22, 2019

(Note: This story was updated on March 22, 2020)

So you’ve got an SPA (Single Page App) and a navigation menu based on anchor tags set in various locations on the page. But the default behavior of an anchor link is to instantly jump to the target … and it’s 2019 … everything is supposed to be done through smooth animations. So, how do you make your links scroll smoothly instead of instantly jump to their destination?

In a function, the answer is: element.scrollIntoView();

Of course, we’ll also want to indicate which section of the SPA is currently active, so we’ll end up doing a bit more than just .scrollIntoView(); I’ll be using ReactJS in my example, but the concepts here are easily transferrable to any JavaScript code-base.

The basic scrolling anchor link

Let’s start by building a MenuItem component, which represents a single link on our menu:

Note that this component is going to expect to receive props — specifically an itemName that it can use to both label itself in the actual Menu and to identify the location to which it will scroll, by element id.

The magic here is really all within element.scrollIntoView() on line 29 … and if all we wanted to do was smoothly scroll between anchored sections of our SPA, we would be done. But wouldn’t it be better if we could automatically detect which MenuItem should show as ‘active’ as the user scrolls down the page? After all, on multi-page apps, the menu usually indicates which page the user is currently visiting.

Next Steps

For this bit of code, we are going to step out one level to our Menu component, which contains all of our MenuItem components (I, personally, am a fan of Atomic Design). We’re also going to use:

element.getBoundingClientRect(); and window.addEventListener();

The requirements we need to fulfill are:

1 — We need to detect the vertical (Y) scroll boundaries of each section

2 — We need to detect the current vertical scroll position of the viewport

3 — We need to detect if the current position indicates that the active section has changed and communicate that change if so.

Detecting the boundaries of each section

It is in this step that we will use element.getBoundingClientRect(); In short, we will identify each of our elements by their destination anchor tag and assume that this location indicates the ‘top’ of the active section. It should be noted here that I have used <h2> elements as anchor destinations as they are valid destinations and serve as great section headers. The bottom of a section is simply the location at which the next section has its top. So … if Section 2 has its top at 2000px, then 1999px is where Section 1 ends.

Note that we use a MutationObserver in the code above, which will call our getAnchorPoints() function any time a watched mutation (such as a page resize or the addition of an element to the page tree) occurs. Adding this Observer helps us to maintain accurate anchor points for our local links by adjusting them anytime the page is changed in some way that might affect the pixel-depth of our anchor targets.

Detect the current vertical scroll position of the viewport

We’ve already learned how to get the current scroll depth on a page in the code above — we use window.scrollY. However, we need to constantly be monitoring this scroll position, not just acquiring it when getAnchorPoints() is called. How is that done? With window.addEventListener(); which is actually included in the useEffect() hook in the code above, after the MutationObserver has been instantiated. We then link it to the handleScroll() function below.

A caution here — you do not want to be performing very many operations on-scroll. It’s an event that fires frequently and any operations performed within a function called by the listener are best nested within a conditional statement that qualifies whether or not the operation actually needs to occur.

With the pieces listed above, we have everything needed to put together a fully functional, smooth-scrolling, menu for a single page app. The full Menu component code would look something like what I’ve put together below:

Enjoy this post? Find it useful? Follow us and keep up with all our Javascript and general-coding posts.

--

--

Kenneth Ahlstrom
The Coder’s Guide to Javascript

Developer, Snowboarder, Travel Enthusiast, Adventurer, ENTP. When I write, it’s because I have something to say.