Skip to content

가로 스크롤

Jeong-Taeho edited this page Nov 10, 2023 · 2 revisions

가로 스크롤

요즘 웹앱에서 많이 볼 수 있는 가로스크롤을 만들어보자.

Event

  • onMouseDown : 마우스를 눌렀을 때 실행되는 이벤트
  • onMouseMove : 마우스를 움직일 때 실행되는 이벤트
  • onMouseUp : 마우스를 땔 떼 실행되는 이벤트

구현

우선적으로 이번 프로젝트에서는 8개의 카테고리를 선택하는 부분에서 앱 크기에서 가로스크롤이 가능하도록 구현해야 했다. 하나의 컨테이너처럼 쓸 div요소를 만들고 ref로 만들어 주었다. ref로 만든 이유는 이 요소에 접근이 가능하도록 하기 위해서이다.

가로스크롤을 구현하기 위해선 생각보다 신경써야 할 부분이 많았다. 내가 드래그(터치) 한 상태 인지를 구분하는 state, 마우스 드래그(터치)가 시작되는 위치가 어디인지를 기록하는 state, 마지막으로 사용자가 한번만 이용하고 다시 쓰지 않는 컴포넌트가 아니기 때문에 위의 3개의 이벤트들을 포함하는 한 번의 루프가 돈 다음의 스크롤 위치를 기록하는 상태이다.

  const containerRef = useRef<HTMLDivElement | null>(null);
  const [dragState, setDragState] = useState<boolean>(false); // 드래그 상태
  const [startX, setStartX] = useState<number>(0); // 처음 스타트 지점을 기록
  const [currentScroll, setCurrentScroll] = useState<number>(0); // 한 번의 루프가 지난 후 스크롤 위치를 기록

이제 각각의 이벤트들을 관리하는 핸들링 함수를 만들어보자.

handleMouseDown

const handleMouseDown: MouseEventHandler<HTMLDivElement> = (event) => {
    setDragState(true);
    setStartX(event.clientX);
    setCurrentScroll(containerRef.current?.scrollLeft || 0);
  };

이 함수에서는 drag가 시작됨을 알림과 동시에 시작 지점을 기록하는 상태를 변경해주었다. 가장 중요한 부분은 이 함수에서 현재 스크롤 위치를 기록하는 상태를 변경해 줌으로써 move 이벤트에서는 직전 루프에서의 스크롤 위치부터 변경을 시작할 수 있었다.

handleMouseMove

const handleMouseMove: MouseEventHandler<HTMLDivElement> = (event) => {
    if (containerRef.current && dragState) {
      const scrollLeft = startX - event.clientX + currentScroll;
      containerRef.current.scrollLeft = scrollLeft;
    }
  };

move 함수에서는 당연하게도 드래그(터치)를 한 상태에서만 함수 안의 코드가 실행되도록 하고 down 이벤트에서 기록된 시작 지점을 기준으로 move 이벤트 에서의 clientX를 빼주었다.

만약 내가 500 지점에서 시작해서 마우스를 왼쪽으로 움직였다면 clientX 는 시작지점보다 작은 숫자를 가지게 되니까 두 값을 뺀 값은 양수가 될 것이고 오른쪽으로 움직인다면 clientX가 더 커져 음수가 될 것이다. 마지막으로 직전의 스크롤 위치를 더해준다면 내가 직전에 어떤 스크롤 위치에서 이 만큼 움직였다! 이런 로직이 완성된다. 내 div 요소의 스크롤 가로 값을 가지는 scrollReft 에 이 값을 넣어주자 드래그 후 스크롤이 되는 걸 볼 수 있었다.

처음 currentScroll 값은 0이 될 것이고 내가 500 지점에서 시작해서 왼쪽으로 이동해서 clientX가 300 이라고 생각한다면 scrollReft는 200이 될 것이고 그 값이 한 번의 루프가 끝난 뒤의 가로 스크롤 값이 되는 것이다. 200 만큼 움직인 거니까 당연히 사용자는 200 만큼 이동한 부분에서의 카테고리 아이콘을 볼 수 있다.

handleMouseUp

 const handleMouseUp = () => {
    if (containerRef.current && dragState) {
      setDragState(false);
    }
  };

이 함수에서는 한 번의 루프가 끝났다는 걸 알려주기 위해 drag 상태를 false로 초기화 해주면 된다.

최적화를 위해 쓰로틀링을 걸어준다거나 다른 좋은 방법도 분명히 존재하겠지만 추후 계속 보완해 나가야 겠다.

완성

20231110_215017.mp4

Clone this wiki locally