Make a Performant Menu

Leo Lin
3 min readJan 27, 2022

Performance is User Experience. So this post will show how I create a performant menu.

Below is the performance recording for my menu under 6x slowdown CPU (with 8 categories and each category has 10 items). You can see there are no dropping frames when I scroll through all the 80-items long menu.

Context

I want to build a menu with some anchors attached to it. Those anchors help users scroll to exact category they are looking for. And I want to give users an overview of where they are, what is the current category they are watching.

In order to accomplish my goal. I know that I need to make something that can listen to the scroll event. Find a way to share the current category information through the app. And build a mechanism to prevent unnecessary renders.

Oh, one last thing to say. I also want to activate the last anchor chip when users scroll to the bottom.

Technology Overview

Here are the primary technologies used in this project:

Design Overview

Combine complex events into one data stream

I expect the final data stream looks like the following. Please allow me using [marble diagram](https://rxmarbles.com/) here.

Imagine there are four categories. And the user scrolls the viewport top to bottom then goes back to top.

Complete code for my useSubscribeToScrollSpyGroup is here.

Detect what users are browsing

I use intersectionObserver instead of a scroll event listener. This way, sites no longer need to do anything on the main thread to watch for this kind of element intersection, and the browser is free to optimize the management of intersections as it sees fit.

Complete code for my spyTargetWithIntersectionObserver is here.

Prevent unnecessary renders by observable

It can cause unnecessary rerenders if using state to manage those in view categories. With `Subject`, we can defer the computation and provide a way to subscribe to. Therefore, use RxJS to transform those menu category is in view notifications into an observable stream. In this way, components can easily subscribe and unsubscribe to this stream and then do whatever they need by operators.

Complete code for my ScrollSpyGroupManagerProvider is here.

Remember last state when navigation back to Menu

Cause I need to get the last emitted value from the combine$. I choose BehaviorSubject instead of Subject. It’s a variant of Subject that requires an initial value and emits its current value whenever it is subscribed.

Reusable

The ScrollSpyGroupManagerProvider can spy not only one single list. If there are two list to spy, I can make them into two groups by providing groupName when using its API.

Smooth scroll animation

Use requestAnimationFrame to implement the animation for scrolling. This method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.

For example, create a scrollWindowVerticallyTo utility to handle scrolling window to a anchor:

Complete code for my scrollWindowVerticallyTo is here. And there is also a scrollElementHorizontallyTo here.

The Complete Code

The complete source code is in this GitHub repository — https://github.com/wtlin1228/menu-with-anchor. Feel free to clone it then measure the performance yourself.

Reference

--

--