우리 프로젝트의 캐릭터샵페이지가 디자인 되었다.
이에 따라서, 각 아이템들을 드래그슬라이드형식으로 바꾸어 줘야한다.
=> 생각해보니, 모바일웹으로 만들고 있어서 자동으로 터치슬라이드가 되어서 굳이 안 만들어도는 된다.
일단 굉장히 친절하게 설명된 아래 블로그를 따라할 것이다.
마우스 드래그로 좌우 스크롤 구현 (ft. React)
아이템들을 행으로 나열하고 설정해놓은 width를 넘어갈 때 overflow-x: scroll로 스크롤을 만들어 보일 수 있게 구현했습니다. 이때 스크롤 바를 움직이는 게 아닌 모바일 환경에서 옆으로 밀어서 이
velog.io
아래는 구현해야할 모습.
드래그슬라이드를 만들기 위한 순서부터 짜보자.
해야할 순서
- 일단 모든 아이템들이 일렬로 늘어선 박스로 보여주어야한다.
- 그리고 화면을 넘어가는 부분은 hidden처리를 해준다.
- 드래그이벤트를 넣어, 슬라이드를 끌어서 넘길 수 있도록 만들어주자.
현재 캐릭터샵 페이지모습
css도 그렇고, 건드려야할 부분들이 많다. 일단 가장 먼저, 화면에서 보이는 아이템들을 일렬로 늘어서게하고 넘쳐나는 부분은 hidden처리를 해주자!
스크롤 바를 없앴으면, 이제 직접 DOM요소에 접근하여 해당 DOM의 scrollLeft를 구해주어야 한다.
여기서 이제부터 사용할 변수와 이벤트의 개념을 먼저 알아보자.
사용할 이벤트
- onMouseDown
마우스 왼쪽 버튼 누르고 있는 상태입니다. - onMouseUp
마우스 왼쪽 버튼 뗀 상태입니다. - onMouseMove
마우스를 움직이는 상태입니다. ( 클릭 하던 안 하던 상관없이) - onMouseLeave
DOM에서 마우스가 벗어났는지 체크하는 이벤트입니다.
사용할 변수
- DOM.scrollWidth
스크롤 할 수 있는 총 길이 - DOM.clientWidth
설정한 max width ( 화면에 보이는 스크롤의 길이 ) - DOM.scrollLeft
스크롤 가장 왼쪽 (DOM.scrollLeft = 0 ) 부터 이동한 스크롤 길이. 즉 DOM.scrollLeft 길이만큼 스크롤 이동 - mouseEvent.pageX
onMouseDown시 x 좌표.
사실 위의 개념이 생소했지만, 알고 나니 드래그슬라이드를 쉽게 만들 수 있을 것 같다는 생각이 든다.
지금부터 다시 머릿속으로 코드를 짜보자.
일단 최종적으로 도달해야할 결론은 마우스를 누르고 뗐을 때의 화면을 보여주면 되는 것이고,
그 후로 다시 마우스를 눌렀을 때, 이동된 스크롤바만큼 다시 X좌표를 설정해주고 땠을 때의 화면을 보여주면 된다.
일단 pageX는 페이지의 X축 좌표를 말한다. 그리고 scrollLeft는 왼쪽으로부터 이동된 스크롤의 길이다.
마우스를 눌렀을 때의 좌표는 위의 두 가지를 더한 좌표여야한다.
두개를 더하지 않고, 그냥 페이지의 X축 좌표만으로 좌표를 설정하면 어떤 일이 일어날까?
마우스를 떼고, 다시 드래그를 할 때마다 스크롤바의 처음 위치부터 시작하는 것을 볼 수 있다. 그렇기에, 마우스를 눌렀을 때의 좌표는 pageX의 좌표에서 스크롤바가 움직인 만큼 더해주어야한다.
//드래그가 시작되면 startX에 시작좌표를 넣어준다.
const onDragStart = (e) => {
e.preventDefault();
setIsDrag(true);
setStartX(e.pageX + dragRef.current.scrollLeft);
};
이제 마우스를 누르고 슬라이드를 한 후, 뗐을 때 스크롤바의 위치가 그 자리에 설정되어야 한다.
그렇다면, 마우스를 뗏을 때인 onMouseLeave 이나, onMouseUp가 작동되면 false로 변하면서 스크롤바의 위치가 정해져야 한다.
그리고 마우스가 움직이고 있을 때는, 계속해서 스크롤바의 위치값을 추적한다. 결국 최종코드는 아래와 같다.
//직접 DOM요소에 접근
const dragRef = useRef(null);
//스크롤의 시작과 끝을 정하기 위해 true, false 구분
const [isDrag, setIsDrag] = useState(false);
//마우스를 눌렀을 떄의 좌표값 설정을 위한 변수
const [startX, setStartX] = useState();
const onDragStart = (e) => {
e.preventDefault();
//마우스를 눌렀을 때부터 드래그 시작
setIsDrag(true);
//마우스를 눌렀을 떄의 좌표값 설정
setStartX(e.pageX + dragRef.current.scrollLeft);
};
const onDragEnd = () => {
//마우스를 떼면, 드래그 끝
setIsDrag(false);
};
const onDragMove = (e) => {
if (isDrag) {
console.log("시작위치", startX);
//마우스를 움직일 때마다 , 스크롤위치를 추적
dragRef.current.scrollLeft = startX - e.pageX;
console.log("최종 스크롤위치", dragRef.current.scrollLeft);
}
};
이제 멋지게 만든 함수들을 아래와 같이 이벤트에 넣어준다.
<AvatarItemListCtn
ref={dragRef}
onMouseDown={onDragStart}
onMouseMove={onDragMove}
onMouseUp={onDragEnd}
onMouseLeave={onDragEnd}
>
아래는 최종결과물이다.
끝난 줄 알았ㅉ;??
개발자라면 여기서 멈추면 안된다. 현재 마우스를 누르고 드래그를 할때마다 엄청난 수의 이벤트가 찍힌다. onMouseMove에서 계속해서 스크롤바의 위치를 추적하고 있기 때문이다. 이것을 최적화를 위해 최소한으로 줄여줘야한다.
그러기 위한 방법에는 Debounce와 Throttle 두 가지 방법이 있다.
내가 간단하게 정리한 두 가지 방법의 개념이다.
Debounce는 특정시간이 지나면, 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것
Throttle은 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
내 생각에는 쓰로틀이 좀 더 유용하지 않을까?
=> 디바운스는 예를 들어, 설정시간을 10초로 한다면 결국, 10초 내에 이벤트가 발생하지않으면 마지막 이벤트를 발생시키기때문에 10초안쪽으로 이벤트가 계속 발생할 경우 무의미하다
허나 쓰로틀은 10초를 설정하면, 10초마다 이벤트가 발생하기때문에 좀 더 최적화에 알맞다고 생각했다.
쓰로틀함수는 그냥 이해만 했다면, 복붙해서 사용하면 된다.
const throttle = (func, ms) => {
let throttled = false;
return (a) => {
//현재 false 이므로, 실행시작!
if (!throttled) {
// 다시 true값으로 바꿔준다.
// 그렇다면 안의 코드가 돌고나면, if문에 의해 다시 닫히게 된다.
throttled = true;
// 설정한 ms시간 뒤, false가 되면서 다시 실행시작!
//여기서 중요한 점은 비동기함수라 끝날때까지 기다리지않고, 진행된다는 점
setTimeout(() => {
func(a);
throttled = false;
}, ms);
}
};
};
이제 쓰로틀함수에 드래그이벤트를 넣어주면 된다. 끝!
비동기함수를 이해한다면, 자연스럽게 쓰로틀함수를 이해할 수 있다.
'프로젝트 > 보드윗 (보드게임원 매칭 서비스)' 카테고리의 다른 글
[ 항해99 실전프로젝트 ] interceptor를 활용해 refresh token 관리 (0) | 2022.11.25 |
---|---|
[ 항해99 실전프로젝트 ] 중간발표까지 기술적 의사결정 (0) | 2022.11.24 |
[ 항해99 실전프로젝트 ] 로고를 SVG로 다루어보자! ( 2 ) (0) | 2022.11.21 |
[ 항해99 실전프로젝트 ] 로고를 SVG로 다루어보자! (1) (0) | 2022.11.21 |
[ 항해99 실전프로젝트 ] 디테일페이지를 모달창으로 바꾸기 (0) | 2022.11.21 |