카드를 클릭했을때는 해당 링크로 이동하고 드래그했을때는 다음 카드로 넘어가도록 하고싶다.
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를 넘었을 때 (다음 카드를 보고싶은 명백한 사용자의 의도가 나타났을 때) 해당 링크이동 없이 다음 카드로 넘어가도록 동작하는 것이다.
위 코드처럼 하면 된다.
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에서 참고해서 만든건데 드래그의 움직임들을 미세하게 잡아서 드래그없이 클릭했을때만 링크로 이동하도록 했다.