[JS] 마우스 Drog & Drop을 이용하여 박스를 이동시켜보자.

2021. 1. 4. 23:19Javascript/응용

 최근에 마우스 드래그 앤 드롭을 이용하여 박스를 이동시키는 기능을 구현한 적이 있었다. 금방 구현할 줄만 알았는데, 많은 시간이 걸렸다. 어려웠던 경험을 정리하고 내 것으로 확실히 만들기 위해 다시 한 번 이 기능을 만들어보았다.

 

 

 따라서 이번 포스팅에서는 마우스 이벤트를 이용하여 드래그 앤 드롭(Drag & Drop)을 이용한 박스 이동 기능을 어떻게 구현했는지에 관하여 써보고자한다. 백문이불여일견(白聞不如一見)이라 했다. 구현 영상은 아래와 같다.

 

 

완성본

 

 

위 기능을 구현하기 위해서는 다음과 같은 마우스 이벤트가 필요하다.

 

  • mousedown: 마우스 버튼이 눌릴 때 발생하는 이벤트.
  • mouseup: 마우스 버튼이 떼어질 때 발생하는 이벤트.
  • mousemove: 마우스 포인터가 이동할 때 발생하는 이벤트.
  • click: 클릭은 mousedown + mouseup 이라고 생각하면 된다.

 

 

 그렇다면, 위 마우스 이벤트들은 어떤 움직임과 관련이 있는 것일까? 먼저, Box를 움직이기 위해서는 두 가지 전제조건이 필요하다.

 

  1. Box 클래스에는 position:absolute라는 CSS 속성을 주고, 상위 클래스인 Container에는 position:relative 속성을 줌으로써, Box 클래스가 Container 클래스 범위 내에서만 움직일 수 있도록 해야한다.(범위 설정)
  2. Box 클래스mousedown 이벤트를 주고, document 또는 document.bodymouseup, mousemove 이벤트를 줘야한다. (움직임)

 

 첫 번째 조건범위와 관련된 것이다. 첫 번째 조건앞 뒤 근거가 매우 타당한 문장이라고 생각되기 때문에 따로 설명하지 않고 HTML, CSS 코드만 올리도록 하겠다. 단, HTML 클래스에 대한 이해와 CSS 포지셔닝(positioning)에 대한 이해가 부족하다면 다시 공부해서 오는 것을 추천한다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>마우스 드래그 앤 드롭</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="area">
        <div class="area__box"></div>
    </div>
    <script src="index.js"></script>
</body>
</html>
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body{
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center; 
    align-items: center;
}

.area{
    width: 1200px;
    height: 800px;
    background-color: rgba(0, 0, 0, 0.2);
    position: relative;
}

.area__box{
    position: absolute;
    top: 0;
    left: 0;
    width: 200px;
    height: 200px;
    background-color: red;
    cursor: pointer;
}

 

 이제 두 번째 조건, 움직임에 대해 이야기를 해보려고 한다. 마우스로 박스를 움직이게 하기 위해서는 세 가지 동작이 필요하다. 첫째, 박스에 마우스를 갖다대고 왼쪽 버튼을 계속 누르고 있는다(mousedown). 둘째, 왼쪽 버튼을 누른 즉시 박스 이동이 시작됨을 전역으로 알려야한다. 셋째, 마우스를 눌렀을 때 컨테이너(Container) 박스를 기준으로 x,y 좌표브라우저 맨 왼쪽 위를 기준으로 한 마우스 포인터 좌표기억한다. [그림 2]는 mousedown 시에 어떤 동작을 취해야 하는지를 나타내는 그림이다. 컨테이너 맨 왼쪽을 기준으로해서 x축 방향으로 박스의 좌측 모서리까지의 거리(bx)20px이라고 가정하자. 그리고 브라우저 맨 왼쪽을 기준으로해서 x축 방향으로 클릭한 포인터까지의 거리(px)230px이라고 가정하자. 우리는 이 두 개를 반드시 기억해야만한다.

 

[그림 2] bx와 px 위치

따라서 이를 코드로 작성하면 다음과 같다.

 

const container = document.querySelector(".area");
const box = container.querySelector(".area__box");

const {width:containerWidth, height:containerHeight} = container.getBoundingClientRect();
const {width:boxWidth, height:boxHeight} = box.getBoundingClientRect();
let isDragging = null;
let originLeft = null;
let originTop = null;
let originX = null;
let originY = null;

box.addEventListener("mousedown", (e) => {
    isDragging = true; // 마우스 이동 시작을 전역으로 알림
    originX = e.clientX; //브라우저를 기준으로 한 마우스 x축 포인터
    originY = e.clientY; //브라우저를 기주능로 한 마우스 y축 포인터
    originLeft = box.offsetLeft; //container를 기준으로 한 bx좌표
    originTop = box.offsetTop; //container를 기준으로 한 by좌표
});

 

 그리고 잡았으니 mousemove를 이용해서 움직이면 된다. 움직임의 원리마우스 포인터가 움직인 거리(50px)에 bx(20px)를 더한 값을 box.style.left 속성에 넣어주면 된다. 만약, bx를 더해주지 않을 경우, 박스는 컨테이너를 기준으로 50px 떨어진 위치로 이동된 후, 부자연스럽게 움직이는 박스를 볼 수 있을 것이다.

 

[그림 3] 움직임의 원리

 

document.addEventListener("mousemove", (e) => {
    if(isDragging){ //마우스 이동 시작을 전역으로 알렸을 경우에만 적용
        const diffX = e.clientX - originX; // 이동한 거리(x)
        const diffY = e.clientY - originY; // 이동한 거리(y)
        // 컨테이너 기준 처음 박스 좌표 + 이동한 거리(x, y)
        box.style.left = originLeft + diffX + "px"; 
        box.style.top = originTop + diffY + "px";
    }
});

 

 

 위 코드를 실행시켜 박스를 이동시켜보면 박스가 컨테이너 범위를 벗어나는 이상한 현상을 발견할 것이다. 우리의 목적은 박스를 컨테이너 범위 내에서만 움직이게 하는 것이기 때문에 이를 수정해야만 한다. 그러기 위해서는 컨테이너의 너비와 높이, 박스의 너비와 높이를 알아야만 한다. 

 

 

 우리는 박스의 top, left 속성에 값을 대입해서 박스를 움직이게 만들었다. 박스와 컨테이너는 포지셔닝에 의한 부모-자식 관계이기 때문에 박스의 좌표는 컨테이너를 기준으로 한다. 또한 박스는 맨 위쪽 끝과 맨 왼쪽 끝 꼭지점에 의해 움직인다는  사실을 알아야한다. [그림 4]를 보면 이해할 수 있을 것이다.

 

[그림 4] 박스의 움직임과 좌표

 

 [그림 4]너비가 1200px, 높이가 500px인 컨테이너종속된 박스의 좌표에 따른 위치를 나타낸 것이다. 만약 가로 세로가 200px인 크기를 갖는 박스의 left값을 1200px, top값을 0px을 준다면, 컨테이너 오른쪽 경계선을 넘어갈 것이다.

하지만, 박스의 left 값을 1000px, top 값을 300px만큼 준다면 박스는 딱 맨 오른쪽 밑 꼭지점에 붙어있을 것이다. 

 

 

 따라서, 박스의 위치는 박스의 크기에 영향을 받기 때문에, 컨테이너 범위 안에서만 박스를 움직이게 하려면 범위를 정해줘야 한다. 컨테이너의 너비와 높이가 1200px, 500px이므로, x축의 앤드 포인트는 1000px가 될 것이고, y축의 앤드 포인트는 300px이 될 것이다.

 

 

 또한, 왼쪽 경계선을 벗어나면 left는 마이너스 부호를 갖게 된다. 마우스 포인터가 왼쪽 경계선을 벗어나더라도, 박스는 왼쪽 끝에 위치해야만 한다

 또한, 위쪽 경계선을 벗어나면 top은 마이너스 부호를 갖게 되므로, 마우스 포인터가 위쪽 경계선을 벗어나더라도, 박스는 위쪽 끝에 위치해야만 한다.

 

 

이에 근거하여 우리는 다음과 같이 코드를 작성할 수 있다.

document.addEventListener("mousemove", (e) => {
    if(isDragging){
        const diffX = e.clientX - originX;
        const diffY = e.clientY - originY;
        
        // containerWidth는 container.getBoundingClientRect().width
        // containerHeight는 container.getBoundingClientRect().height
        // boxWidth는 box.getBoundingClientRect().width
        // boxHeight는 box.getBoundingClientRect().height
        const endOfXPoint = containerWidth - boxWidth;
        const endOfYPoint = containerHeight - boxHeight;
        
        // left가 -값이 되면 0이 최댓 값이 되므로 박스는 경계선을 벗어날 수 없음.
        // top이 -값이 되면 0이 최댓 값이 되므로 박스는 경계선을 벗어날 수 없음.
        box.style.left = `${Math.min(Math.max(0, originLeft+ diffX), endOfXPoint)}px`;
        box.style.top = `${Math.min(Math.max(0, originTop + diffY), endOfYPoint)}px`;
    }
});

 

그리고 마우스 버튼을 뗐을 경우의 코드는 다음과 같다.

document.addEventListener("mouseup", (e) => {
    isDragging = false;
});

 

자바스크립트 전체  코드

 

const container = document.querySelector(".area");
const box = container.querySelector(".area__box");

const {width:containerWidth, height:containerHeight} = container.getBoundingClientRect();
const {width:boxWidth, height:boxHeight} = box.getBoundingClientRect();
let isDragging = null;
let originLeft = null;
let originTop = null;
let originX = null;
let originY = null;

box.addEventListener("mousedown", (e) => {
    isDragging = true;
    originX = e.clientX;
    originY = e.clientY;
    originLeft = box.offsetLeft;
    originTop = box.offsetTop;
});

document.addEventListener("mouseup", (e) => {
    isDragging = false;
});

document.addEventListener("mousemove", (e) => {
    if(isDragging){
        const diffX = e.clientX - originX;
        const diffY = e.clientY - originY;
        const endOfXPoint = containerWidth - boxWidth;
        const endOfYPoint = containerHeight - boxHeight;
        box.style.left = `${Math.min(Math.max(0, originLeft+diffX), endOfXPoint)}px`;
        box.style.top = `${Math.min(Math.max(0, originTop + diffY), endOfYPoint)}px`;
    }
});

 

 

 이상으로 마우스 Drog & Drop을 이용한 박스 이동에 대한 포스팅을 마치도록 하겠다. 쉽게 될 줄 알았는데, 생각보다 어려워서 놀랬다. 자세히 적어 놓았으니, 까먹었을 때 보면 다시 떠오르겠지....ㅎㅎ