Skip to content
es-toolkit실무 경험

Building a Scroll To Top Button Optimized with es-toolkit's throttle

2 min readWorkMoved from my Tistory blogRead the Korean original

Figure 1. Autowini Shipping Schedule screen

A few times now, I have built the Scroll To Top button you often see in the bottom-left or bottom-right corner of the screen.

But I think each time I kept wondering, how should I use the scroll event so that it is optimized for performance?

This time I found a clear answer to that question and implemented it in a cleaner way, so I want to share how it went.

I am currently migrating some legacy JSP pages of Autowini Mobile to a React project.

Among those pages, I added a Scroll To Top button to the "Shipping Schedule" page, which shows international shipping information, and this time I was able to build it in a more optimized way than before.

# The inefficiency of my previous approach

Whenever I built a Scroll To Top button before, I used an approach that detected every scroll event directly and managed the state from it.

But with this approach, the event gets called excessively and unnecessarily, which can put a load on performance.

Then I remembered the concepts of debounce and throttle, which I had studied while working on a company's take-home assignment in the past.

Concept How it works Typical use Examples
Debounce When events fire in a row, runs once a set amount of time has passed after the last event Sending an API request after the user finishes typing Search autocomplete, form input validation
Throttle Limits how often the event handler runs to once per set time interval Controlling frequent event calls to optimize performance Scroll events, resize events

For this Scroll To Top button, I decided that throttle was the appropriate choice.

# Why did I choose es-toolkit?

I had been following es-toolkit, an open-source library developed by Toss (a Korean fintech company), for a while, and I also had a passion for contributing to open source, so I decided to use it in this project.

According to the official docs, es-toolkit uses a modern implementation, which gives it the strengths of a very small bundle size and excellent performance.

Smaller bundle size

Compared with lodash, es-toolkit is up to 97% smaller depending on the function. Some utility functions are smaller than 100 bytes, which made it the best choice for keeping bundle size to a minimum.

Better performance

es-toolkit was designed with performance as a priority. It is 2x faster than lodash on average, and for some functions the speed difference goes up to 11x. This is because it is optimized by making active use of modern JavaScript APIs.

For these reasons, I used es-toolkit's throttle to minimize bundle size and maximize performance.

https://es-toolkit.slash.page/ko/reference/function/throttle.html

throttle | es-toolkit

# Implementation

tsx
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import { throttle } from 'es-toolkit';

export default function ScrollToTop() {
  const [isVisible, setIsVisible] = useState(false);

  /* Use the throttle function to keep the scroll event from being called too often */
  const toggleVisibility = throttle(
    () => {
      if (isVisible) return;
      setIsVisible(true);
      setTimeout(() => {
        setIsVisible(false);
      }, 2000);
    },
    2000, // Handle the scroll event at 2000ms intervals
    { edges: ['leading'] } // Do not use the trailing option
    /* 
    If 'leading' is included, the original function runs immediately the first time the throttled function is called.
    If 'trailing' is included, the original function runs once throttleMs milliseconds have passed since the last call to the throttled function.
    */
  );

  /* Function that scrolls the page to the very top */
  const handleScrollToTop = () => {
    window.scrollTo({
      top: 0,
      behavior: 'smooth',
    });
  };

  useEffect(() => {
    window.addEventListener('scroll', toggleVisibility);
    return () => {
      window.removeEventListener('scroll', toggleVisibility);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return <Button onClick={handleScrollToTop} $isVisible={isVisible} />;
}

const Button = styled.button<{ $isVisible: boolean }>`
  position: fixed;
  bottom: 120px;
  right: 10px;
  width: 95px;
  aspect-ratio: 1;
  border: none;
  border-radius: 10px;
  background-repeat: no-repeat;
  background-image: url(${import.meta.env
    .VITE_FILE_BROWSER_URL}/bg_arrow_top.png);
  cursor: pointer;
  opacity: ${(props) => (props.$isVisible ? '0.8' : '0')};
  visibility: ${(props) => (props.$isVisible ? 'visible' : 'hidden')};
  transition: opacity 0.3s, visibility 0.3s;
  z-index: 10;
`;
  • #es-toolkit
  • #scroll to top
  • #throttle
  • #Performance Optimization

More in Work