[React] 원 페이지 스크롤 구현

JUJU·2025년 4월 23일

프론트엔드

목록 보기
4/4

원 페이지 스크롤 구현 가이드

이 문서는 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)
<Base ref={containerRef}>
  <SliderContainer>
    <Slide />
    <Slide />
    <Slide />
  </SliderContainer>
</Base>

2. 슬라이드 이동 구현 방법

2.1 scrollToPage 함수

window.innerHeight를 기준으로 슬라이드 위치 계산:

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 호출:

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. 스타일 구성

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로 분할하고, 사용자 스크롤에 맞춰 정확한 위치로 이동시키는 제어가 핵심입니다. 이를 통해 보다 몰입감 있는 인터랙티브 웹페이지를 제작할 수 있습니다.

profile
백엔드 개발자

0개의 댓글