프로젝트에서 위와 같이 한 페이지 당 50개의 책 정보를 담고 있는 도서 목록 조회를 구현하고 있었다.
그런데 페이지 당 50개가 있다보니 PC의 경우는 괜찮았지만 모바일 환경에서는 다시 위로 올라가려면 많은 스크롤을 해야 했다. 여러 페이지에서 목록 조회가 이루어지고 있었기에 같은 문제가 여러 곳에서 존재할 수 있었다. 그래서 여러 모바일 사이트 및 앱에 존재하는 스크롤의 위치가 어느 정도 밑으로 내려가면 클릭 시 최상단으로 이동할 수 있는 버튼을 제공하기로 했다.
간단하게 버튼 스타일링을 하고,
.scroll-btn {
width: 50px;
height: 50px;
border-radius: 50%;
$bottom: calc(env(safe-area-inset-bottom) + 15px);
@include common.set-fixed(auto, 20, $bottom);
@include common.flex(row, center, center);
right: 15px;
background: white;
border: solid 1px rgba(0, 0, 0, 0.08);
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 8px;
svg {
color: common.$neutral-50;
}
}
다음과 같이 버튼을 구현했다.
'use client'
import { ArrowUp } from '@phosphor-icons/react'
import { throttle } from 'lodash'
import { useEffect, useMemo, useState } from 'react'
const THROTTLE_WAIT = 300
export default function ScrollToTopButton() {
const [isVisible, setIsVisible] = useState(false)
// 스크롤 위치에 따라 버튼의 렌더링 여부를 설정
const handleIsVisible = useMemo(
() =>
throttle(() => {
const { scrollY, innerHeight } = window
setIsVisible(scrollY > innerHeight)
}, THROTTLE_WAIT),
[],
)
// 버튼 클릭 시 15ms마다 step만큼 스크롤 이동
const onClick = () => {
const scrollStep = -window.scrollY / 20
const scrollInterval = setInterval(() => {
if (window.scrollY !== 0) {
window.scrollBy(0, scrollStep)
return
}
clearInterval(scrollInterval)
}, 15)
}
useEffect(() => {
window.addEventListener('scroll', handleIsVisible)
return () => {
window.removeEventListener('scroll', handleIsVisible)
}
}, [handleIsVisible])
return (
<div>
{isVisible && (
<button
type="button"
className="scroll-btn"
aria-label="최상단 이동"
onClick={onClick}
>
<ArrowUp size={25} />
</button>
)}
</div>
)
}
구현 과정
1. 버튼을 눌렀을 때 스크롤이 최상단으로 이동해야 한다.
2. 현재 스크롤의 위치가 viewport 높이보다 크다면 (viewport 높이 값보다 더 밑으로 내려왔다면) 버튼 렌더링
첫 번째로 버튼을 눌렀을 때 스크롤이 최상단으로 이동하기 위해 처음에는 단순히 window.scrollTo(0, 0)을 적용했었다. 그런데 그렇게 하니 최상단으로 이동하긴 하지만 스크롤이 자동으로 위로 슝 올라간다기보단 최상단으로 순간이동하는 듯했다. 우리가 익숙하게 봐왔던 최상단 이동 느낌이 아니었다.
그래서 interval과 scrollBy를 통해 아주 작은 시간마다 작은 간격으로 스크롤의 위치를 위로 이동시켜 결과적으로 빠르게 슝 위로 올라가는 듯한 느낌의 애니메이션을 주었다.
두 번째로 스크롤 이벤트를 등록하여 현재 스크롤의 위치가 viewport 높이 값보다 크다면(밑에 있다면) 버튼을 렌더링 하도록 하였다. throttle을 적용하여 THROTTLE_WAIT(300ms)마다 스크롤 위치를 확인하고, 버튼 렌더링 여부를 결정하도록 했다. 스크롤 이벤트의 경우 아주 미세하게라도 위치가 움직이면 핸들러 함수가 수행되다 보니 throttle을 적용하지 않으면 매우 많은 횟수의 연산이 수행된다. 이는 performance에 악영향을 주게 되므로 피해야 했다.
최상단 이동을 추가하면서 최상단 이동까지의 시간이 단축되었다. 실제 모바일에서 테스트 해봤을 때 최하단에서 최상단 이동까지 약 3초 걸리던 것이 0.4초로 단축되었다. 최상단 이동 버튼을 구현함으로써 사용자 경험을 개선할 수 있어서 뿌듯했다!
'Develop > NextJS' 카테고리의 다른 글
[Next.js] Suspense를 이용한 Skeleton UI 적용하기 (0) | 2024.08.19 |
---|---|
[Next.js] SEO 최적화 & 구글/네이버에 사이트 등록하기 (0) | 2024.08.10 |
[Next.js] 서버 & 클라이언트 컴포넌트에서 토큰 관리하기 (0) | 2024.07.24 |
[Next.js] Data fetching과 Caching Mechanism (0) | 2024.06.12 |
[Next.js] Next.js 작업물 Vercel로 배포하기 (0) | 2023.07.21 |