본문 바로가기

TIL

마우스 감도에 따른 스크롤 불일치 문제

🚨 발생한 문제 

  • ScrollPicker 컴포넌트의 일관되지 않는 스크롤 동작 확인(데스크탑 마우스 설정에 따라 1칸씩, 3칸씩 스크롤 상이)
  • 모바일 퍼스트로 화면 드래그로 스크롤 할때는 문제가 없었으나 웹 뷰 테스트 중 해당 문제가 발생

 

❓원인 

  • 사용자의 마우스 감도 설정에 따라 wheel 이벤트의 발생 빈도 차이가 발생해 다르게 동작함
  • 높은 감도 설정에서는 작은 휠 움직임으로도 여러 번의 이벤트가 발생

 

✅ 해결 과정 

1. isScrolling ref 사용

const isScrolling = useRef(false);
  • 연속 스크롤 입력 시에도 일관된 스크롤을 보장하고 스크롤 중복 실행을 방지하기 위해 사용

 

2. handleWheelScroll 함수 추가

  • 스크롤 속도 최적화 (behavior: 'smooth' 시간 단축, setTimeout 시간 조정)
  • 가독성 향상을 위해 유틸리티 함수로 분리
interface ScrollHandlerParams {
  containerRef: React.RefObject<HTMLDivElement>;
  currentIndex: number;
  setCurrentIndex: (index: number) => void;
  options: string[];
  isScrolling: React.MutableRefObject<boolean>;
  handleScroll: (e: React.UIEvent<HTMLDivElement>) => void;
}

export const handleWheelScroll = (
  e: WheelEvent,
  {
    containerRef,
    currentIndex,
    setCurrentIndex,
    options,
    isScrolling,
    handleScroll,
  }: ScrollHandlerParams,
) => {
  e.preventDefault(); // 커스텀 스크롤 동작을 구현하기 위해 브라우저의 기본 스크롤 동작 방지

  // 이미 스크롤 중이거나 컨테이너 ref가 없으면 함수 실행을 중단 (중복 스크롤 방지)
  if (isScrolling.current || !containerRef.current) return;
  
  // 스크롤 상태 설정
  isScrolling.current = true;
  
  // 마우스 휠 방향에 따른 새로운 인덱스 계산
  const direction = e.deltaY > 0 ? 1 : -1;
  const newIndex = Math.max(
    0,
    Math.min(options.length - 1, currentIndex + direction),
  ); // Math.max와 Math.min으로 인덱스가 유효범위를 벗어나지 않도록

  setCurrentIndex(newIndex); // 새 인덱스로 상태 업데이트
  // 부드러운 스크롤 효과
  containerRef.current.style.scrollBehavior = "smooth";
  containerRef.current.style.transition = "all 100ms";
  containerRef.current.scrollTop = newIndex * 40;

  handleScroll({
    currentTarget: containerRef.current,
  } as React.UIEvent<HTMLDivElement>);

  // 100ms 후에 스크롤 상태를 false로 설정해서 다음 스크롤 입력을 받을 수 있게 상태 초기화
  setTimeout(() => {
    isScrolling.current = false;
  }, 100);
};

 

 

3. wheel 이벤트 리스너를 관리하는 useEffect 추가

  • 컴포넌트 마운트 시 + currentIndex, options 변경 시 실행
  • wheel 이벤트가 발생할 때마다 wheelHandler 가 handleWheelScroll 함수 호출
useEffect(() => {
    const element = containerRef.current;
    if (element) {
      const wheelHandler = (e: WheelEvent) =>
        handleWheelScroll(e, {
          containerRef,
          currentIndex,
          setCurrentIndex,
          options,
          isScrolling,
          handleScroll,
        });

      element.addEventListener("wheel", wheelHandler, { passive: false }); // 스크롤 이벤트 기본동작 제어
     // 컴포넌트 언마운트 or 의존성 값 변경 시 이벤트 리스너 제거
     return () => {
        element.removeEventListener("wheel", wheelHandler);
      };
    }
  }, [currentIndex, options]);

 

 

📌 결과 

  • 기기 마우스 감도 설정과 관계없이 사용자 스크롤에 정확하게 반응하는 모습을 확인할 수 있다!