React 앱을 죽이는 일을 그만하세요: 버터처럼 부드러운 Scroll-To-Top 컴포넌트

발행: (2026년 3월 3일 오전 01:58 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

함정: 모든 픽셀을 추적하기

처음 이 컴포넌트를 만들었을 때, 나는 전형적인 함정에 빠졌다. window.scrollY 값을 React state에 정확히 저장하는 것이다. 사용자가 스크롤할 때마다 픽셀 단위로 상태가 업데이트되어, 컴포넌트가 초당 수백 번 다시 렌더링된다. 이유 없이 막대한 리소스를 잡아먹는다.

해결책: 임계값 상태 & CSS 마법

정확한 스크롤 깊이를 추적하는 대신, 우리는 사용자가 임계값을 넘었는지 만 신경쓴다. (이 경우 300 px). 불리언 하나만 추적하면 React는 두 번만 다시 렌더링한다—버튼이 나타날 때 한 번, 사라질 때 한 번.

import { useState, useEffect } from 'react';
import './ScrollToTop.css';

const ScrollToTop = () => {
    const [isVisible, setIsVisible] = useState(false);

    useEffect(() => {
        const handleScroll = () => {
            // Toggle state only when crossing the 300px mark
            setIsVisible(window.scrollY > 300);
        };

        // The { passive: true } option is crucial for scroll performance!
        window.addEventListener('scroll', handleScroll, { passive: true });

        return () => {
            window.removeEventListener('scroll', handleScroll);
        };
    }, []); // Empty dependency array ensures the listener mounts once

    const handleClick = () => {
        window.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    };

    return (
        
            
                Scroll to Top ⇑
            
        
    );
};

export default ScrollToTop;

스타일링: display: none은 버려라

display: blockdisplay: none 사이를 토글하는 요소는 애니메이션을 적용할 수 없으며, 갑자기 사라졌다 나타난다. 부드러운 페이드 효과를 위해 opacityvisibility를 사용하고, 보이지 않을 때 클릭을 차단하기 위해 pointer-events: none을 함께 적용한다.

.btn {
    padding: 12px 20px;
    border: none;
    border-radius: 8px;
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 1000;
    background-color: slateblue;
    color: white;
    cursor: pointer;
    /* Transition opacity and visibility for a smooth fade */
    transition: opacity 0.4s ease, visibility 0.4s ease, transform 0.3s ease;
    box-shadow: 0 4px 6px rgba(0,0,0,0.2);
}

.btn:hover {
    background-color: orange;
    transform: translateY(-3px);
}

.show {
    opacity: 0.85;
    visibility: visible;
}

.hide {
    opacity: 0;
    visibility: hidden;
    pointer-events: none; /* Prevents invisible clicks */
}

왜 이 컴포넌트가 승자인가

  • 드래그 제로 – 픽셀 추적을 없애면 브라우저가 한결 가벼워진다.
  • 패시브 리스너{ passive: true }는 리스너가 기본 스크롤 동작을 방해하지 않음을 브라우저에 알려, 스크롤을 완벽하게 부드럽게 만든다.
  • 접근성 및 깔끔함aria-label이 스크린 리더에 친화적이며, CSS 애니메이션이 프리미엄 UI 느낌을 준다.

프로젝트에 자유롭게 활용해 보세요! 댓글로 여러분이 React에서 스크롤 이벤트를 어떻게 처리하는지 알려 주세요—표준 useEffect 대신 선호하는 커스텀 훅이 있나요?

0 조회
Back to Blog

관련 글

더 보기 »