React 66장 - 좌표값을 활용한 드래그
포스트
취소

React 66장 - 좌표값을 활용한 드래그

React

참고자료1
참고자료2

드래그 구현하기

  • 페이지를 만들다보니 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 };

추가

  • 부모 요소의 크기를 드래그 할 요소가 넘어갔을 때, 부모 요소 안으로 위치시켜주게 한다.
  • refgetBoundingClientRect()를 이용하면 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이 수정이 되었다.

  • 해결 필요
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.