React 앱을 죽이는 일을 그만하세요: 버터처럼 부드러운 Scroll-To-Top 컴포넌트
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: block과 display: none 사이를 토글하는 요소는 애니메이션을 적용할 수 없으며, 갑자기 사라졌다 나타난다. 부드러운 페이드 효과를 위해 opacity와 visibility를 사용하고, 보이지 않을 때 클릭을 차단하기 위해 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 대신 선호하는 커스텀 훅이 있나요?