[REACT] 리액트 요소 드래그로 옮기기 (React Drag and Drop)

쥬롬의 코드착즙기·2022년 8월 26일
6

TIL : Today I learned

목록 보기
2/10
post-thumbnail
post-custom-banner

리액트 요소 드래그로 옮기기

리액트 요소를 드래그로 옮겨서 기본화면을 개성있게 만들어 주자.

(본 포스팅은 YOUTUBE Bitesize Academi: How to drag and drop a div in react using hooks? (react-use-gesture + react-spring)
영상 내용을 기반으로 합니다.)

⚛ 사용 패키지

  • create-react-app
  • react-use-gesture
  • (optional) react-spring

01. create-react-app으로 리액트 앱 초기화

우선 create-react-app을 통해 react app을 설치해준다.
ctr+shift+(백틱)을 통해 터미널창을 열고, 아래 코드를 각각 입력해준다.

npx create-react-app .

npx로, create-react-app을 실행하는데, 현재 폴더(.)에다가 한다는 뜻이다.
모두 완료하면 npm start를 통해 앱을 실행시켜준다.

npm start

그러면 react app 기본화면이 보인다.

02. useState로 로고 위치 받아오기

가장 먼저 움직여볼 것은 화면 가운데 있는 리액트 로고이다.
로고을 움직이기 위해서 x, y 좌표를 state로 받아오자.

src/app.js에서 최상단에 import를 추가한다.

import {useState} from 'react'

그리고 function App()안에, state를 선언해준다.

function App() {
  const [logoPos, setlogoPos] = useState({x:0, y:0})
  return (
    <div className="App">
///more...

x와 y를 0으로 설정해주고, 로고를 드래그할 때마다 x, y축으로의 증가값(감소값)이 새로운 x와 y로 설정될 것이다.

logo img를 감싸는 div container를 만들어주자. 그리고 style을 다음과 같이 설정한다.

<div className="App">
      <header className="App-header">
        <div style={{
          position: "relative",
          top: logoPos.y,
          left: logoPos.x
        }}>
        <img src={logo} className="App-logo" alt="logo" />
        </div>
///more...

그러면 x와 y 값을 변화시킬 때마다 로고의 위치가 변화한다.
위치 변화가 잘 되는지 확인하기 위해 state 선언부로 돌아가서 x와 y를 임의의 값으로 변경해보자.

  const [logoPos, setlogoPos] = useState({x:50, y:100})

그러면 로고가 이동된 것을 볼 수 있다.

03. useDrag로 드래그 가능하게 만들기

그렇지만 우리가 원하는 것은 사용자가 직접 x, y offset을 입력하는 게 아니라 드래그할 때마다 자동으로 변하게 하는 것이다. 그래서 사용자가 요소를 드래그할 때마다 감지할 수 있는 일종의 콜백 함수가 필요하다. 이 콜백 함수는 원래 로고 좌표로부터 x와 y 방향으로 얼마나 떨어졌는지의 좌표를 가지고 있어야 한다.

이것을 가능하게 만들어주는 것이 react-use-gestureuseDrag hook이다.

우선 react-use-gesture를 다운받아주자.
터미널창에 아래 코드를 입력한다.

npm i react-use-gesture --save

npm으로, i(install: 설치)하며, react-use-gesture를 다운받아, 저장한다는 의미이다.
설치가 완료되면 useDrag hook을 사용하기 위해 최상단에 import를 추가해준다.

import {useDrag} from 'react-use-gesture';

useDrag()는 함수를 반환하는데, 이 함수를 bindLogoPos 변수로 받는다. 이 변수는 logo img를 감싸기 위해 만들었던 container div의 props로 줄 것이다. bindLogoPos()함수 실행 결과가 props로 들어간다.

function App() {
  const [logoPos, setlogoPos] = useState({x:50, y:100})
  const bindLogoPos = useDrag(()=>{}); /*NEW LINE!*/
  return (
    <div className="App">
      <header className="App-header">
        <div {...bindLogoPos()} style={{ /*MODIFIED!*/
          position:"relative",
          top: logoPos.y,
          left: logoPos.x
        }}>
        <img src={logo} className="App-logo" alt="logo" />
        </div>
///more...

참고: bindLogoPos()는 객체를 반환한다. 반환된 객체를 구조분해할당 하기 위해 three dots를 이용해 {...bindLogoPos}로 쓴다.

마지막으로, useDrag()를 마저 완성시켜보자.

react-use-gesture 공식 문서의 State attribute를 확인해보면, useDrag()의 파라미터를 확인할 수 있다.
그 가운데 이용할 것은 offset (초기 위치로부터 떨어진 값)이다.
offset은 [x offset, y offset]의 배열로 반환된다.
setlogoPos를 통해 logoPos state를 offset을 통해 받은 각각의 값으로 변경한다.

  const bindLogoPos = useDrag((params)=>{
    setlogoPos({
      x: params.offset[0],
      y: params.offset[1],
    })
  });

그러면 이제 드래그가 가능해진다!

(드래그할 때 간혹 나타나는 선택 표시를 없애고 싶다면, App.css에서 .App 클래스에 user-select:none;를 추가해준다.)

- 로직 정리

사용자가 드래그함
=> bindLogoPos() 콜백 감지
=> useDrag() 실행
=> setlogoPos()로 x, y offset state 변경
=> 드래그로 위치변화!

04. useSpring으로 더 간단하게!

여기까지 해도 드래그는 할 수 있지만, useState 대신에 react-spring을 사용하면 더 쉽고 간단하게 드래그를 구현할 수 있다.
react-spring은 요소의 이동, 애니메이션 등 시각적 요소를 적용하기 쉽게 만들어주는 패키지이다.

또 설치해주자...

npm i react-spring --save

설치가 완료되면 또 import 해주자...
이제 useState는 안 쓸거니까 지워도 된다.

//import {useState} from 'react';
import {useSpring, animated} from 'react-spring';

logoPos를 useState 대신 useSpring으로 바꾸어주자.

  //const [logoPos, setlogoPos] = useState({x:0, y:0})
  const logoPos = useSpring({x:0, y:0});

다음으로, bindLogoPos를 수정한다.setlogoPos 대신에, set() 메소드로 x, y offset 변경이 가능하다.

  const bindLogoPos = useDrag((params)=>{
    /*setlogoPos({
      x: params.offset[0],
      y: params.offset[1],
    })*/
    logoPos.x.set(params.offset[0]);
    logoPos.y.set(params.offset[1]);
  });

container divstyle도 position, top, left 대신 더 직관적인 x, y로 바꾼다.

<div {...bindLogoPos()} style={{
          /*position:"relative",
          top: logoPos.y,
          left: logoPos.x*/
          x: logoPos.x,
          y: logoPos.y
        }}>
        <img src={logo} className="App-logo" alt="logo" />
        </div>

마지막으로! div container에게 삶을 불어넣어주기 위해 animated.div로 변경한다.

<animated.div {...bindLogoPos()} style={{
          /*position:"relative",
          top: logoPos.y,
          left: logoPos.x*/
          x: logoPos.x,
          y: logoPos.y
        }}>
        <img src={logo} className="App-logo" alt="logo" />
        </animated.div>

그러면 똑같이 드래그가 된다.

- 나머지 요소들도 드래그로 옮기고 싶다면

같은 로직을 사용하면 된다. 아래 첨부할 소스코드 github에서 확인할 수 있다.

05. 소스코드

GITHUB

😊 juurom
부족한 글 읽어주셔서 감사합니다.
오류 수정, 더 나은 제안 댓글 등 감사히 확인하겠습니다.

profile
코드를 짭니다...
post-custom-banner

0개의 댓글