[React] 원 페이지 스크롤 구현
원 페이지 스크롤을 구현하는 방법
원 페이지 스크롤 구현 가이드
이 문서는 React와 styled-components를 활용해 “한 화면씩 넘기는 원 페이지 스크롤” 기능을 구현하는 방법을 설명합니다. 사용자는 마우스 휠을 통해 슬라이드를 하나씩 전환하며, 각 슬라이드는 전체 화면(viewport)을 채우는 구조로 구성됩니다.
1. 시작: 스크롤 이벤트 감지
원 페이지 스크롤의 핵심은 사용자의 휠 스크롤 이벤트를 감지하는 것입니다.
브라우저는 기본적으로 사용자가 마우스 휠을 움직일 때마다 wheel 이벤트를 발생시킵니다.
이 이벤트는 deltaY라는 값을 포함하고 있어, 사용자가 스크롤을 위로 움직였는지(음수) 아래로 움직였는지(양수)를 확인할 수 있습니다.
스크롤 이벤트를 감지하려면 다음을 설정합니다:
- 특정 요소(보통 전체 페이지를 감싸는 div)에 이벤트 리스너를 추가
passive: false옵션을 주어 기본 동작(브라우저의 스크롤)을 막을 수 있음
2. 동작: 현재 슬라이드 인덱스 업데이트
휠 스크롤이 감지되면 다음 단계는 현재 사용자가 보고 있는 슬라이드 인덱스를 갱신하는 작업입니다.
이를 위해 내부적으로 유지되는 currentPage 상태값을 사용하여:
- 아래로 스크롤 시:
currentPage를 1 증가 - 위로 스크롤 시:
currentPage를 1 감소 - 첫 페이지와 마지막 페이지에서는 더 이상 이동하지 않도록 제한
이 로직은 사용자 스크롤에 따라 어떤 슬라이드를 보여줄지를 판단하는 핵심 기준이 됩니다.
3. 결과: 화면 이동 (스크롤 위치 제어)
슬라이드 인덱스가 변경되면, 해당 인덱스에 맞는 화면 위치로 스크롤을 프로그래밍적으로 이동시킵니다.
이를 위해 scrollTo 또는 scrollTop 값을 직접 조작하여:
- 예를 들어,
2번째 슬라이드 = 2 * 화면 높이 (100vh)로 계산 - 부드러운 이동을 위해
behavior: 'smooth'옵션 사용
이 과정을 통해 마우스 휠 한 번에 정확히 한 슬라이드씩 이동하는 느낌을 줄 수 있습니다.
4. 부가 처리: 스크롤 이벤트 최적화
스크롤 이벤트는 매우 자주 발생할 수 있으므로, 다음과 같은 처리가 추가로 필요합니다:
- 스크롤 중복 방지: 스크롤 중에는 추가 입력을 막기 위해 잠깐 상태를 잠그는 방식 사용 (debounce 또는 flag 활용)
- 페이지 범위 체크: 0보다 작거나, 슬라이드 총 개수를 초과하지 않도록 제한
- 모바일 대응: 터치 이벤트 (
touchstart,touchend)도 별도 감지 필요
5. 결론
원 페이지 스크롤은 마우스 휠을 감지하고, 현재 페이지를 기준으로 다음 화면 위치를 계산해 스크롤하는 구조입니다. 이 과정은 스크롤 이벤트 감지 → 현재 위치 판단 → 위치 이동이라는 순서를 반복하면서 동작합니다.
구현은 간단해 보이지만, UX 관점에서 부드럽고 안정적인 전환을 위해 이벤트 제어와 스크롤 상태 관리가 중요합니다.
1. 구조 개요
기본 구조는 다음과 같습니다:
Base: 전체 스크롤을 제어하는 컨테이너 (스크롤 대상)SliderContainer: 모든 슬라이드를 포함하는 요소 (높이 = 슬라이드 개수 x 100vh)- 각
Slide: 개별 페이지 (높이 100vh)
1
2
3
4
5
6
7
<Base ref={containerRef}>
<SliderContainer>
<Slide />
<Slide />
<Slide />
</SliderContainer>
</Base>
2. 슬라이드 이동 구현 방법
2.1 scrollToPage 함수
window.innerHeight를 기준으로 슬라이드 위치 계산:
1
2
3
4
5
6
7
8
9
const scrollToPage = (index: number) => {
if (!containerRef.current) return;
const targetScrollTop = index * window.innerHeight;
containerRef.current.scrollTo({
top: targetScrollTop,
behavior: "smooth",
});
setCurrentPage(index);
};
2.2 마우스 휠 이벤트 처리
사용자의 휠 스크롤을 감지해 현재 페이지 상태에 따라 scrollToPage 호출:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
useEffect(() => {
const handleWheel = (e: WheelEvent) => {
e.preventDefault();
if (e.deltaY > 0 && currentPage < slides.length - 1) {
scrollToPage(currentPage + 1);
} else if (e.deltaY < 0 && currentPage > 0) {
scrollToPage(currentPage - 1);
}
};
const container = containerRef.current;
if (container) {
container.addEventListener("wheel", handleWheel, { passive: false });
}
return () => {
if (container) {
container.removeEventListener("wheel", handleWheel);
}
};
}, [currentPage, slides.length]);
3. 스타일 구성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Base = styled.div`
height: 100vh;
overflow-y: auto;
scroll-behavior: smooth;
`;
const SliderContainer = styled.div`
height: calc(100vh * 슬라이드 개수);
`;
const Slide = styled.div`
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
`;
슬라이드는 반드시 height: 100vh로 설정되어야 하며, 컨테이너는 슬라이드 개수만큼 높이를 갖도록 해야 스크롤이 제대로 작동합니다.
4. 주의사항 및 개선 포인트
- IntersectionObserver를 활용하여 슬라이드 진입 시 애니메이션 트리거 가능
- 모바일 터치 이벤트 처리 필요시
touchstart/touchend이벤트 별도 추가 - 슬라이드 개수가 많을 경우
throttle또는debounce를 통해 이벤트 최적화 필요
5. 결론
React 환경에서 원 페이지 스크롤을 구현하기 위해서는 각 섹션을 정확히 100vh로 분할하고, 사용자 스크롤에 맞춰 정확한 위치로 이동시키는 제어가 핵심입니다. 이를 통해 보다 몰입감 있는 인터랙티브 웹페이지를 제작할 수 있습니다.