<React> drag slider

yezee·2022년 11월 17일
3

React

목록 보기
9/23
post-thumbnail

밀리의서재를 클론하면서 느낀거는 밀리의서재는 캐러셀과 슬라이더 천국이다 👼🏻
넘치는 패기로 라이브러리 없이 구현해보고 싶었다 하지만 슬라이더의 벽은 높았고... 친절한 다른 블로그를 보고 배웠다

사전지식이 없으면 너무 어려워용

screenX, screenY
사용자 모니터 화면을 기준으로 한 좌표를 표시
사용자 모니터의 왼쪽 상단 모서리가 (0, 0)

pageX, pageY
전체 문서를 기준으로 한 좌표를 표시
문서를 표현할 때 스크롤이 생긴다면, 특정 지점의 pageY 좌표값은 페이지가 스크롤 될때마다 변경(아래로 계속내려가서 값이 변경된다)

clientX, clientY
브라우저에서 사용자에게 웹페이지가 보여지는 영역을 기준으로 좌표를 표시
따라서, 스크롤바가 움직이더라도, 특정 지점의 clientX, clientY의 값은 동일

offsetX, offsetY
좌표를 출력하도록 하는 이벤트가 걸려있는 DOM node를 기준으로 좌표를 표시
만약 특정 div 영역에서 offsetX, offsetY를 출력한다면,
div의 왼쪽 상단 모서리 부분의 offsetX, offsetY의 값은 (0, 0)

Drag silde

마우스 드래그로 좌우 스크롤 구현

아이템들을 행으로 나열하고 설정해놓은 width를 넘어갈 때 overflow-x: scroll로 스크롤을 만들어 보일 수 있게 구현
이때 스크롤 바를 움직이는 게 아닌 마우스 드래그로 구현
기본 틀

//DragCarousel.jsx
  return (
    <div>
      {carousel && (
        <div
          className="card-wrap">
          {carousel.map(item => {
            const { id, coverImg, title, author } = item;
            return (
              <Card
                key={id}
                title={title}
                author={author}
                coverImg={coverImg}/>);
          })}
        </div>
      )}
    </div>
  );

css

/*DragCarousel.scss*/
.card-wrap  {
  display: flex; /* div가 가로방향*/
  overflow-x: scroll; /* div에 가로 스크롤을 줘서 옆으로 길게 보이게함*/
}

/*overflow-x와 함께오는 scroll bar 제거*/
.card-wrap::-webkit-scrollbar {
  display: none;
}

useRef 사용하여 DOM에 직접 접근

좌우 슬라이드 스크롤의 움직임은 해당 DOM의 scrollLeft(클릭한 시점부터 클릭을 땐 시점까지)로 움직인다
해당 DOM의 scrollLeft를 얻기 위해 useRef를 사용하여 DOM에 접근

//DragCarousel.jsx
const DragCarousel = () => {
  const scrollRef = useRef(null);

  return (
    <div>
      {carousel && (
        <div className="card-wrap" ref={scrollRef}>
          ...
        </div>
      )}
    </div>
  );
};

코드구현

1️⃣ onDragStart
startX는 현재 클릭한 pageX와 움직인 스크롤의 길이 scrollLeft를 합친 값
스크롤이 이동하지 않았을 때는 문제가 없지만 스크롤이 이동된 상태에서 클릭을 한다면, 브라우저의 width의 pageX값이 설정이 돼 순간적으로 앞쪽으로 스크롤이 됩니다. 이를 막기 위해 scrollLeft를 더해 현재 x의 위치를 계산
2️⃣ onDragEnd
onMouseUp, onMouseLeave 이벤트가 발생했을 때 isDrag를 false로 설정
3️⃣ onMouseMove

  • 마우스를 움직임 감지로 클릭과 상관없이 발생합니다. 드래그 효과를 주기 위해 isDrag변수가 true 일 때 발생하도록 설정했습니다
  • scrollLeft의 값을 설정: 처음 클릭한 x의 좌표 startX에서 움직이면서 변하는 e.pageX를 빼서 이동한 스크롤길이를 scrollLeft에 넣어준다
    (scrollLeft->스크롤 가장 왼쪽 (DOM.scrollLeft = 0 ) 부터 이동한 스크롤 길이)
    onDragMove = e => { if (isDrag) { scrollRef.current.scrollLeft = startX - e.pageX}};

코드 리팩토링

평상 시 이벤트 제거하기

onMouseMove 이벤트는 클릭 여부와 상관없이 해당 DOM에 위치하면 발생합니다.
이를 막기 위해 isDrag가 false 일 때 작동하지 않도록 설정
isDrag ? onThrottleDragMove : null

너무 많은 이벤트 발생

onMouseMove로 수많은 이벤트가 발생합니다
이벤트를 delay 시켜 끊어주는 Throttle을 사용하여 해결했습니다
delay를 50ms로 설정

const scrollRef = useRef(null);
  const [isDrag, setIsDrag] = useState(false); //드레그 중인지의 상태확인
  const [startX, setStartX] = useState(); //처음 클릭한 x좌표 
  const 1️⃣onDragStart = e => {
    e.preventDefault();
    setIsDrag(true);
    setStartX(e.pageX + scrollRef.current.scrollLeft);
  };
  const 2️⃣onDragEnd = () => {
    setIsDrag(false);
  };
  const 3️⃣onDragMove = e => {
    if (isDrag) {
      const { scrollWidth, clientWidth, scrollLeft } = scrollRef.current;

    scrollRef.current.scrollLeft = startX - e.pageX;
      
      if (scrollLeft === 0) {
        setStartX(e.pageX); //가장 왼쪽일 때, 움직이고 있는 마우스의 x좌표가 곧 startX로 설정.
      } else if (scrollWidth <= clientWidth + scrollLeft) {
        setStartX(e.pageX + scrollLeft); //가장 오른쪽일 때, 움직이고 있는 마우스의 x좌표에 현재 스크롤된 길이 scrollLeft의 합으로 설정
      }
    }
  };
// 쓰로틀 구현
  const throttle = (func, ms) => {
    let throttled = false;
    return (...args) => {
      if (!throttled) {
        throttled = true;
        setTimeout(() => {
          func(...args);
          throttled = false;
        }, ms);
      }
    };
  };

  const delay = 50;
  const onThrottleDragMove = throttle(onDragMove, delay);

    return (
    <div>
      {carousel && (
        <div
          className="card-wrap"
          onMouseDown={onDragStart}
          onMouseMove={isDrag ? onThrottleDragMove : null}
          onMouseUp={onDragEnd}
          onMouseLeave={onDragEnd}
          ref={scrollRef}
        >
          {carousel.map(item => {
            const { id, coverImg, title, author } = item;
            return (
              <Card
                key={id}
                title={title}
                author={author}
                coverImg={coverImg}
              />
            );
          })}
        </div>
      )}
    </div>
  );  

디바운스(Debounce)와 스로틀(Throttle)

DOM 이벤트를 기반으로 실행하는 자바스크립트를 성능상의 이유로 이벤트(event)를 제어(제한)하는 방법
스크롤(scroll wheel), 트랙패드, 스크롤 막대를 드래깅을 하게 되면 수많은 스크롤 이벤트가 발생하게 됩니다.
매번 스크롤 이벤트에 대한 콜백(callback)이 발생하고 매우 큰 리소스를 잡아먹습니다==성능문제 발생
ThrottleDebounce이벤트 핸들러가 많은 연산을하는 경우 제약을 걸어 제어할 수 있는 수준으로 이벤트를 발생시키는 것을 목표 로 하는 기술입니다
🚖 ebounce

  • 이벤트를 그룹화하여 특정시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술입니다. 즉, 순차적 호출을 하나의 그룹으로 "그룹화"
  • 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
    🚘 throttle
  • 이벤트를 일정한 주기마다 발생하도록 하는 기술
  • Throttle 의 설정시간으로 1ms 를 주게되면 해당 이벤트는 1ms 동안 최대 한번만 발생
  • 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
    몇 초에 한 번, 또는 몇 밀리초에 한 번씩만 실행되게 제한을 두는 것

사용한 이벤트와 변수들

-이벤트-
onMouseDown 마우스 왼쪽 버튼 누르고 있는 상태입니다.
onMouseUp 마우스 왼쪽 버튼 뗀 상태입니다.
onMouseMove 마우스를 움직이는 상태입니다. ( 클릭 하던 안 하던 상관없이)
onMouseLeave DOM에서 마우스가 벗어났는지 체크하는 이벤트입니다.
-변수-
DOM.scrollWidth 스크롤 할 수 있는 총 가로길이
DOM.clientWidth 설정한 max width ( 화면에 보이는 스크롤의 길이 )
DOM.scrollLeft 스크롤 가장 왼쪽 (DOM.scrollLeft = 0 ) 부터 이동한 스크롤 길이. 즉 DOM.scrollLeft 길이만큼 스크롤 이동
mouseEvent.pageX onMouseDown시 x 좌표(어디에서 마우스 스크롤을 시작했는지 알려준다)

라이브러리 사용하기

  • slick slider
  • react-multi-carousel
    npm install react-multi-carousel --save

(참조)
https://velog.io/@tunakim/%EB%A7%88%EC%9A%B0%EC%8A%A4-%EB%93%9C%EB%9E%98%EA%B7%B8%EB%A1%9C-%EC%A2%8C%EC%9A%B0-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84-ft.-React

https://webclub.tistory.com/607

profile
아 그거 뭐였지?

0개의 댓글