반응형
리액트에서 스크롤,드래그 둘 다 되는 슬라이더
회사에서 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
반응형
'개발 > React' 카테고리의 다른 글
React : code spliting을 이용한 성능 최적화 (0) | 2023.05.18 |
---|---|
useHasScroll.tsx : 컴포넌트의 스크롤바 유무를 boolean으로 리턴 (0) | 2023.04.30 |
react18 : flushSync (0) | 2023.04.10 |
useDeferredValue : 업데이트를 지연시키고 싶을때 사용 (0) | 2023.03.04 |
부분렌더링 Input 컴포넌트 (0) | 2023.03.01 |