react-slick card 클릭시 이동하고 drag할때는 클릭 막기

SangBooom·2022년 1월 26일
4
post-custom-banner

상황

카드를 클릭했을때는 해당 링크로 이동하고 드래그했을때는 다음 카드로 넘어가도록 하고싶다.

해결

const Introduce = (): React.ReactElement => {
  const [dragging, setDragging] = useState(false);
  
  ...
  
  const handleBeforeChange = useCallback(() => {
    setDragging(true);
  }, [setDragging]);

  const handleAfterChange = useCallback(() => {
    setDragging(false);
  }, [setDragging]);

  const onClickCard = useCallback(
    (path) => (e: React.SyntheticEvent) => {
      if (dragging) {
        e.stopPropagation();
        return;
      }
      if (path.includes('https')) {
        window.open(path, '_blank');
      } else {
        router.push(path);
      }
    },
    [dragging],
  );

  const settings = {
    ...
    draggable: true,
    beforeChange: handleBeforeChange,
    afterChange: handleAfterChange,
  };

  return (
    ...
      <CustomCarousel {...settings}>
        {MainCardList.map((v) => (
          <Card
            key={v.pointColor}
            pointColor={v.pointColor}
            className="card"
            onClick={onClickCard(v.path)}
          >
            <Image src={v.imgSrc} width={192} height={145} />
            <MainTitleWrap pointColor={v.pointColor} className="card-title">
              <div className="first-title">{v.firstTitle}</div>
              <div className="second-title">{v.secondTitle}</div>
            </MainTitleWrap>
          </Card>
        ))}
      </CustomCarousel>
   ...
  );
};

export default Introduce;

설명에 필요하지 않은 코드들은 정리했습니다.

일단 드래그를 할 수 있도록 설정이 기본이 되어야하니 draggable: true를 해준다.

그 다음은 캐러셀의 기본 동작 개념을 어느정도 알고 있어야한다.
onMouseDown -> onMouseDrag -> onMouseUp의 순서로 드래그를 진행했을때 이동거리가 카드의 width/2를 넘지못하면 제자리로 돌아간다.
만약 카드의 width/2를 넘는다면 크기를 넘은 방향으로 카드가 넘어가게 된다.
여기서 onMouseUp 이후에 바로 beforeChange가 동작하고 트랙이 transform: translate3d((방향(+,-)+카드크기)px, 0px, 0px) 만큼 움직이고 이동이 끝나면 바로 afterChange가 실행된다.

내가 하고싶은건 드래그의 이동거리가 카드 width/2를 넘었을 때 (다음 카드를 보고싶은 명백한 사용자의 의도가 나타났을 때) 해당 링크이동 없이 다음 카드로 넘어가도록 동작하는 것이다.
위 코드처럼 하면 된다.

+ 만약 드래그의 이동거리가 카드 width/2를 넘지 못했을때도 클릭이벤트를 막고싶다면? (드래그없이 클릭만 했을때 해당링크로 이동할 수 있도록)

import React, { useRef, useState, useMemo, useCallback, useEffect } from 'react';
import _throttle from 'lodash/throttle';

import { IHomeReview } from '@reducers/homeReview/homeReview.d';
import { ReviewSliderWrap, ReviewImgWrap, ReviewImg } from './Review.styles';

interface IProps {
  targetData: IHomeReview[];
  onClickImg: (url?: string) => () => void;
}

const ReviewSlider = ({ targetData, onClickImg }: IProps): React.ReactElement => {
  const sliderRef = useRef<HTMLDivElement>(null);
  const [isStart, setIsStart] = useState(false);
  const [isDraged, setIsDrag] = useState(false);
  const [startX, setStartX] = useState(0);

  const isOverflow = useMemo(() => {
    if (process.browser) {
      const contentsWidth = targetData.length * 274 + (targetData.length - 1) * 20;
      if (contentsWidth > window.innerWidth) return contentsWidth / 2 - window.innerWidth / 2;
    }
    return null;
  }, [targetData.length]);

  const onDragStart = useCallback((e) => {
    e.preventDefault();
    if (sliderRef.current) {
      setIsStart(true);
      setStartX(e.pageX + sliderRef.current.scrollLeft);
    }
  }, []);

  const onDragEnd = useCallback(
    (e) => {
      setIsStart(false);
      if (isDraged) {
        e.stopPropagation();
        setIsDrag(false);
      }
    },
    [isDraged],
  );

  const onDragMove = useMemo(
    () =>
      _throttle((e) => {
        if (sliderRef.current) {
          if (isStart) {
            if (!isDraged) {
              setIsDrag(true);
            }
            const { scrollWidth, clientWidth, scrollLeft } = sliderRef.current;
            sliderRef.current.scrollLeft = startX - e.pageX;
            if (scrollLeft === 0) {
              setStartX(e.pageX);
            } else if (scrollWidth <= clientWidth + scrollLeft) {
              setStartX(e.pageX + scrollLeft);
            }
          }
        }
      }, 10),
    [isStart, startX, isDraged],
  );

  useEffect(() => {
    if (sliderRef.current && isOverflow) {
      sliderRef.current.scrollLeft = isOverflow;
    }
  }, [isOverflow]);

  return (
    <ReviewSliderWrap
      ref={sliderRef}
      onMouseDownCapture={onDragStart}
      onMouseMoveCapture={onDragMove}
      onMouseUpCapture={onDragEnd}
      onMouseLeave={onDragEnd}
    >
      {targetData.map(({ id, img, url }) => (
        <ReviewImgWrap key={id} className="reviewImgWrap" onMouseUp={onClickImg(url)}>
          <ReviewImg src={img} alt="reviewImg" width={274} height={274} priority />
        </ReviewImgWrap>
      ))}
    </ReviewSliderWrap>
  );
};

export default ReviewSlider;

이건 stackOverflow에서 참고해서 만든건데 드래그의 움직임들을 미세하게 잡아서 드래그없이 클릭했을때만 링크로 이동하도록 했다.

profile
끊임없이 떨어지는 물방울이 바위를 뚫는다
post-custom-banner

0개의 댓글