아이템들을 행으로 나열하고 설정해놓은 width
를 넘어갈 때 overflow-x: scroll
로 스크롤을 만들어 보일 수 있게 구현했습니다. 이때 스크롤 바를 움직이는 게 아닌 모바일 환경에서 옆으로 밀어서 이동하는 것처럼 마우스 드래그로 구현해봤습니다.
const Categories = () => {
return (
<div>
{categories && (
<div className="categories">
{categories.map((category) => (
<div className="category" key={category.name}>
<Link to={`/category/${category.text}`}>
<div className="category-box"></div>
<p>{category.text}</p>
</Link>
</div>
))}
</div>
)}
</div>
);
};
.categories {
display: flex;
overflow-x: scroll;
}
// scroll bar 제거 ( chrome 환경)
.categories::-webkit-scrollbar {
display: none;
}
scrollLeft
로 움직입니다.scrollLeft
를 얻기 위해 useRef
를 사용하여 DOM에 접근했습니다.const Categories = () => {
const scrollRef = useRef(null);
return (
<div>
{categories && (
<div className="categories" ref={scrollRef}>
...
</div>
)}
</div>
);
};
사용할 이벤트
onMouseDown
onMouseUp
onMouseMove
onMouseLeave
사용할 변수
DOM.scrollWidth
DOM.clientWidth
DOM.scrollLeft
DOM.scrollLeft = 0
) 부터 이동한 스크롤 길이. 즉 DOM.scrollLeft
길이만큼 스크롤 이동mouseEvent.pageX
onMouseDown
시 x 좌표.const Categories = () => {
const scrollRef = useRef(null);
1️⃣const [isDrag, setIsDrag] = useState(false);
const [startX, setStartX] = useState();
2️⃣const onDragStart = (e) => {
e.preventDefault();
setIsDrag(true);
setStartX(e.pageX + scrollRef.current.scrollLeft);
};
3️⃣const onDragEnd = () => {
setIsDrag(false);
};
const onDragMove = (e) => {
4️⃣if (isDrag) {
6️⃣ scrollRef.current.scrollLeft = startX - e.pageX;
}
};
return (
<div>
<div
className="categories"
onMouseDown={onDragStart}
onMouseMove={onThrottleDragMove}
onMouseUp={onDragEnd}
onMouseLeave={onDragEnd}
ref={scrollRef}
></div>
</div>
);
onMouseMove
는 왼쪽 버튼을 떼도 발생합니다. 드래그 효과를 주기 위해 isDrag
변수가 true
일 때 발생하도록 설정했습니다.startX
는 현재 클릭한 pageX
와 움직인 스크롤의 길이 scrollLeft
를 합친 값입니다. 스크롤이 이동하지 않았을 때는 문제가 없지만 스크롤이 이동된 상태에서 클릭을 한다면, 브라우저의 width
의 pageX
값이 설정이 돼 순간적으로 앞쪽으로 스크롤이 됩니다. 이를 막기 위해 scrollLeft
를 더해 현재 x
의 위치를 계산했습니다.onMouseUp
, onMouseLeave
이벤트가 발생했을 때 isDrag
를 false
로 설정했습니다.isDrag
가 true
일 때x
의 좌표 startX
와 움직이면서 변하는 e.pageX
로 scrollLeft
의 값을 설정했습니다.마우스를 움직였을 때 더 이상 스크롤을 할 수 없는 상태에서, 반대쪽으로 스크롤을 움직이고 싶을 때, 처음 마우스를 클릭한 지점을 지나쳐야 스크롤이 움직입니다. ( startX
의 scrollLeft
값이 변하지 않기 때문에 )
이를 해결하기 위해 DOM
의 속성들을 이용해 해결했습니다.
const onDragMove = (e) => {
if (isDrag) {
const { scrollWidth, clientWidth, scrollLeft } = scrollRef.current;
scrollRef.current.scrollLeft = startX - e.pageX;
1️⃣if (scrollLeft === 0) {
setStartX(e.pageX);
} 2️⃣else if (scrollWidth <= clientWidth + scrollLeft) {
setStartX(e.pageX + scrollLeft);
}
}
};
x
좌표가 곧 startX
로 설정.x
좌표에 현재 스크롤된 길이 scrollLeft
의 합으로 설정onScroll
이벤트와 같이 onMouseMove
도 수많은 이벤트가 발생했습니다.
Debounce
대신 이벤트를 delay 시켜 끊어주는 Throttle
을 사용하여 해결했습니다.
delay를 일반적인 250ms를 설정하면 너무 불편해 100ms로 설정했습니다.
// 쓰로틀 구현
// util.js
const throttle = (func, ms) => {
let throttled = false;
return (...args) => {
if (!throttled) {
throttled = true;
setTimeout(() => {
func(...args);
throttled = false;
}, ms);
}
};
};
// 컴포넌트
const onDragMove = (e) => {
...
};
const delay = 100;
const onThrottleDragMove = throttle(onDragMove, delay);
return(
...
<div
className="categories"
onMouseDown={onDragStart}
onMouseMove={onThrottleDragMove}
onMouseUp={onDragEnd}
onMouseLeave={onDragEnd}
ref={scrollRef}
>
...
)
onMouseMove
이벤트는 클릭 여부와 상관없이 해당 DOM에 위치하면 발생합니다.
이를 막기 위해 isDrag
가 false
일 때 작동하지 않도록 설정했습니다.
return(
...
<div
className="categories"
onMouseDown={onDragStart}
onMouseMove={isDrag ? onThrottleDragMove : null}
onMouseUp={onDragEnd}
onMouseLeave={onDragEnd}
ref={scrollRef}
>
...
)
안녕하세요 설명 감사히 봤습니다. 질문이 하나 있는데요 오른쪽으로 드래그할 때는 스무스하게 되는데 왼쪽으로 드래그할 때는 버벅이는데 혹시 이 부분 수정하는 방법 팁 얻을 수 있을까요?