React
드래그 구현하기
- 페이지를 만들다보니
drag
기능을 구현하고 싶어졌다. - 어떤 방법으로 시도할지 찾아보며 다양한 라이브러리를 보았지만, 처음 작성하다보니 내가 직접 작성하여 구현하고 싶었다.
- 내가 클릭하고 드래그 하는 이벤트가 발생할 때, 좌표값을 저장해놓았다가, 이동한 만큼의 거리를 컴포넌트의 위치값으로 활용하면 될 것 같았다.
요소 선택과 상태값 저장
1
2
3
4
5
6
7
8
9
10
// useExample.ts
// 클릭했을 때 포인트 값
const [startPos, setStartPos] = useState({ x: 0, y: 0 });
// 포인트가 이동한 거리를 계산할 값
const [movePos, setMovePos] = useState({ left: 0, top: 0 });
// drag 상태
const [drag, setDrag] = useState(false);
// 요소 선택
const containerRef = useRef<HTMLDivElement>(null);
이벤트 작성
- 자료들을 찾아보며
onDrag
이벤트를 활용하여 작성한 코드도 보았다. - 익숙하지 않은 이벤트였지만 대략적인 방법은 비슷해 보였고, 조금 더 익숙한 마우스 이벤트를 활용해보려고 했다.
- 따라하기 식으로
useEffect
를 사용하고 이벤트와useEffect
와의 관계를 찾아보면서 마우스 이벤트와 발생하는 버그가 있고 이를 해결하기 위해useEffect
를 사용한다고 보았지만, 좌표값에 따른event
의 재호출을 위해 사용해야 하는 것인지, 직관적인 이벤트 실행을 위해 요소에 함수를 직접 설정하는 것과는 어떤 차이가 있는지 계속 알아보아야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// example.tsx
const {movePos, dragStart, containerRef} = useExample()
<Container onMouseDown={dragStart} ref={containerRef} $left={movePos.left}, $top={movePos.top}></Container>;
const Container = styled.div<ExampleProps>`
// ...
position: abolute;
left: ${props => props.$left + "px"};
top: ${props => props.$top + "px"};
`
// useExample.ts
const dragStart = (e:MouseEvent) => {
setStartPos({x: e.clientX, y: e.clientY})
setDrag(true)
};
useEffect(() => {
const dragHandler = (e: MouseEvent) => {
if(!e.current)
// 이동한 좌표에서 초기 좌표값
const left = e.clientX - startPos.x
const top = e.clientY - startPos.y
setMovePos({left, top})
}
const dragEnd = () => {
setDrag(false)
}
if(drag){
document.addEventListener("mousemove", dragHandler);
document.addEventListener("mouseup", dragEnd);
}
return = () => {
docuemnt.removeEventListener("mousemove", dragHandler);
document.removeEventListener("mouseup", dragEnd)
}
},[startPos, movePos])
return {containerRef, startPos, movePos}
수정
- 초기에도 좌표값이 이동됨에 따라 렌더링이 계속 발생했다.
- 지정한 요소의
left, top
값을 훅의 좌표값으로 설정하다보니, 지속적으로 변하는 좌표값에 대한 렌더링이었다. - 무수히 많은 렌더링을 확인했지만 사용상 큰 문제가 없었기에 넘어갔는데, 이를 사용하는
Example.tsx
에 다른 요소들 및 함수들이 추가되며useMemo,useCallback
으로도 해결이 불가능할 정도의 버벅임이 생겼다. - 그래서 차선책으로 선택한 방법이 드래그를 하고 마우스를 놓았을 때에만, 마우스의 위치 이동 좌표값을 계산하여
left, top
값을 지정해주려고 한다. - 우선 기본적으로 드래그가 안 되는 요소인
div
의 움직임을 파악하기 위해draggable
속성을 부여하여 드래그가 가능하게 만든다. - 그리고 훅의 코드를 수정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 동일 코드 생략
// Example.tsx
<Container onMouseDown={dragStart} onDragEnd={dragEnd} draggable></Container>;
// useExmaple.ts
const dragStart = (e: MouseEvent) => {
setStartPos({ x: e.clientX, y: e.clientY });
};
const dragEnd = (e: MouseEvent) => {
if (!e.current) {
const left = e.clientX - startPos.x;
const top = e.clientY - startPos.y;
setMovePos({ left, top });
}
};
return { movePos, dragStart, dragEnd };
추가
- 부모 요소의 크기를 드래그 할 요소가 넘어갔을 때, 부모 요소 안으로 위치시켜주게 한다.
ref
의getBoundingClientRect()
를 이용하면left, top...
의 좌표값을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
const parentRef = useRef<HTMLDivElement>(null);
const childRef = useRef<HTMLDivElement>(null);
const parentSize = parentRef?.current.getBoundingClientRect();
const childSize = childRef?.current.getBoundingClientRect();
// 좌측 밖으로 벗어났을 때
if (childSize.left < parentSize.left) {
setMovePos((prev) => ({ ...prev, left: 0 }));
}
// 이하 동일
left, top
으로 활용될movePos
가 변경될 때마다 요소가 넘어가는지 확인하고, 다시 좌표값을 수정해주기 위해useEffect
를 사용했다.- 그러다 마주한 에러가
set...
에 대한 함수는useEffect
에서의 사용을 제한하고 있는데, 이는 내가 의존성 배열에 작성한movePos
가 변할 때마다 다시 실행되기 때문에,useEffect
내부에서 다시 재설정된movePos
값에 의해 렌더링이 다시 발생하기 떄문이다. - 조건문으로 걸러주기 떄문에 다시 코드를 읽을 때
set...
으로 값이 재설정 될 일은 없지만, 코드가 잘못 작성되면 무한 렌더링이 발생한다.
추가
- 그리하여 조건문에 관한 코드를
dragEnd
함수의 마지막에 작성하여 상태값이 변경되고 난 후 좌표값을 확인하여 다시 상태값을 지정해주려고 했다. 하지만 마우스를 놓았을 때 처음 지정된 좌표값으로 고정이 되고 (if문 발동 X) 다음 이벤트가 발생할 때에
left, top
이 수정이 되었다.- 해결 필요