본문 바로가기
개발/React

리액트에서 스크롤,드래그 둘 다 되는 슬라이더

by 안뇽! 2023. 4. 26.
반응형

리액트에서 스크롤,드래그 둘 다 되는 슬라이더

회사에서 framer-motion으로 만든 슬라이더가 이상하게 작동했다.

드래그와 스크롤이 따로 놀고 있음

위와 같이 마우스로 카드를 잡아 끌었을때 스크롤은 움직이지 않았다. 드래그와 스크롤의 싱크를 맞추는 게 어려웠다.

고민하다가, 그냥 쌩으로 만들기로 했다. 이번 작업은 DOM의 scroll 이벤트 공부가 많이 되었다.

 

onMouseDown

const [dragging, setDragging] = useState(false);
const [scrollLeft, setScrollLeft] = useState(0);

 const handleMouseDownEvent = (e: MouseEvent<HTMLDivElement>) => {
 // 마우스 클릭하면
    setDragging(true);
    if (containerRef.current) {
      setClickPoint(e.pageX); // clickPoint는 처음 클릭한 지점
      setScrollLeft(containerRef.current.scrollLeft);// 스크롤움직인 거리, 처음은 0
    }
  };

onMouseMove


const handleMouseMoveEvent = (e: MouseEvent<HTMLDivElement>) => {
    if (!dragging) return; // onMouseDownEvent에서 dragging=true가 아니면 작동하지 않음

    e.preventDefault();

    if (containerRef.current) {
      // clickPoint는 onMouseDown에서 처음 클릭한 위치가 할당된다.
      // walk = 마우스를 끌고간 최종 위치 - 처음 마우스를 클릭한 위치
      // 오른쪽에서 왼쪽으로 끌면 음수이다.
      const walk = e.pageX - clickPoint; 

      // walk가 음수, 즉 오른쪽에서 왼쪽으로 끌었을때 scrollLeft - walk는 양수가 되면서
      // containerRef의 scrollLeft가 증가하면서 스크롤이 오른쪽으로 움직인다.
      containerRef.current.scrollLeft = scrollLeft - walk;
    }
  };

위 onMouseDown, onMouseMove 두개가 핵심이고 전부다.

전체 코드

import { useRef, useState, MouseEvent } from 'react';
import styled from 'styled-components';

import CloudyArea from '../molecules/Slider/CloudyArea'; // 이건 없어도 됨

interface CloudyProps {
  hasCloudyArea?: boolean;
  cloudyAreaBgColor?: string;
}
interface Props extends CloudyProps {
  children: React.ReactNode;
}

export const DragSlider = ({ children, hasCloudyArea = false, cloudyAreaBgColor, ...rest }: Props) => {
  const containerRef = useRef<HTMLDivElement>(null);

  const [dragging, setDragging] = useState(false);
  const [clickPoint, setClickPoint] = useState(0);
  const [scrollLeft, setScrollLeft] = useState(0);

  const handleMouseDownEvent = (e: MouseEvent<HTMLDivElement>) => {
    setDragging(true);
    if (containerRef.current) {
      setClickPoint(e.pageX);
      setScrollLeft(containerRef.current.scrollLeft);
    }
  };

  const handleMouseMoveEvent = (e: MouseEvent<HTMLDivElement>) => {
    if (!dragging) return;

    e.preventDefault();

    if (containerRef.current) {
      const walk = e.pageX - clickPoint;

      containerRef.current.scrollLeft = scrollLeft - walk;
    }
  };

  return (
    <Container {...rest}>
      {hasCloudyArea && <CloudyArea cloudyAreaBgColor={cloudyAreaBgColor} />}
      <Slider
        ref={containerRef}
        onMouseDown={handleMouseDownEvent}
        onMouseLeave={() => setDragging(false)}
        onMouseUp={() => setDragging(false)}
        onMouseMove={handleMouseMoveEvent}
      >
        {children}
      </Slider>
    </Container>
  );
};

const Container = styled.div`
  position: relative;
  overflow: hidden;
  width: 100%;

  img {
    pointer-events: none;
  }
`;

const Slider = styled.div`
  display: flex;
  width: max-content;
  cursor: grab;

  width: 100%;
  overflow-y: hidden;
  overflow-x: scroll;
  padding-bottom: 21px;

  &::-webkit-scrollbar {
    width: 9px;
  }
  &::-webkit-scrollbar-thumb {
    width: 248px;
    height: 9px;
    border-radius: 100px;
    background: #d9d9d9;
  }
  &::-webkit-scrollbar-track {
    background: transperant;
  }
`;

참고

리액트 말고 DOM으로 만드는 dragable scroll slider

 

e.pageX 설명글

 

 

반응형