❗❗모던 JavaScript 튜토리얼에 있는 코드를 참고하여 작성했습니다.❗❗
🌱 이벤트 흐름
mousedown 👉 mousemove 👉 mouseup
먼저, mousedown
에서 움직임이 필요한 요소를 준비한다. 이때 기존 요소의 복사본을 만들거나, 해당 요소에 클래스를 추가하는 등 원하는 형태로 작업할 수 있다.
이후 mousemove
에서 position:absolute의 left∙top을 변경하고, 마지막으로 mouseup
에서는 드래그 앤 드롭 완료와 관련된 모든 작업을 수행한다.
마우스로 드래그 앤 드롭을 시도하면 공을 찍어 올려 복사된 공을 드래그하는 동작을 볼 수 있다.
브라우저 자체적으로 이미지나 요소에 대한 드래그 앤 드롭을 지원하기 때문이며, 브라우저에서 제공하는 기능이 자동 실행되어 작성한 코드와 충돌된다.
그래서 ondragstart를 사용하여 브라우저의 기본 드래그 기능을 막아서 충돌을 방지한다.
element.ondragstart = function() {
return false;
};
드래그 시작 시 요소를 기준으로 포인터의 초기 이동을 기억하고 (shiftX∙shiftY) 드래그하는 동안 유지한다.
즉 클릭 시 마우스 좌표에 따라 요소가 움직이지 않게, 요소의 왼쪽, 위쪽 좌표를 빼준다.
// onmousedown(추가할 코드)
let shiftX = event.clientX - ball.getBoundingClientRect().left;
let shiftY = event.clientY - ball.getBoundingClientRect().top;
// onmousemove
// 공은 고정된 포지션을 갖는다.
ball.style.left = event.pageX - shiftX + 'px';
ball.style.top = event.pageY - shiftY + 'px';
원래는 공의 가장자리에서 mousedown을 하게 되면, 마우스 포인터 아래로 공이 갑자기 점프 되는 부작용이 발생했는데, 위의 코드를 추가하고 나서 현재 위치에서 포인터를 부드럽게 따라간다.
지금까지 봐왔던 예제에서는 공을 ‘어디서나’ 드롭할 수 있었다. 하지만, '파일’을 '폴더’나 다른 곳에 놓듯 실생활에서는 보통 한 요소를 다른 요소에 드롭한다.
즉 ‘드래그 가능한’ 요소를 ‘드롭 가능한’ 요소에 두는 것을 해보자!
알아야 할 것:
어떻게 하면 좋을까?
잠재적으로 놓을 수 있는 요소에 여태 했던 것처럼 드래그 앤 드롭 이벤트를 사용하여, mouseover∙mouseup 핸들러를 설정하면, 동작하지 않는다.
드래그하는 동안 드래그 할 수 있는 요소가 항상 다른 요소 위에 있다는 것이 문제가 된다. 마우스 이벤트의 맨 위 요소에서만 이벤트가 발생하고, 맨 위 요소의 아래에는 이벤트가 발생하지 않기 때문이다.
예를 들면, 아래 두 개의 <div>
요소가 있으며, 파란색 요소 전체를 덮는 빨간색 요소가 있다. 빨간색 요소가 제일 위에 있어서 파란색 요소의 이벤트를 잡을 방법이 없다.
<style>
div {
width: 50px;
height: 50px;
position: absolute;
top: 0;
}
</style>
<div style="background:blue" onmouseover="alert('never works')"></div>
<div style="background:red" onmouseover="alert('over red!')"></div>
드래그할 수 있는 요소도 빨간색 요소가 파란색 요소를 덮은 예제와 같다. 공은 항상 다른 요소 위에 있어 이벤트가 발생한다. 반면에 하위 요소에 설정한 어떠한 핸들러도 동작하지 않는다.
그러므로 잠재적 드롭 가능한 요소에 핸들러를 넣는 처음에 생각했던 방법은 실제로 동작하지 않는다. 그러면 무엇을 해야 할까?
document.elementFromPoint(clientX, clientY)
라는 메서드를 사용해야 한다.
주어진 윈도우 기준 좌표에서 가장 많이 중첩된 요소를 반환한다. (윈도우 밖의 좌표는 null)
다음과 같이 마우스 이벤트 핸들러에서 포인터 아래에 드롭 가능성을 감지할 수 있다.
// 마우스 이벤트 핸들러에서
ball.hidden = true; // (*) 드래그하는 요소를 숨긴다.
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
// elemBelow는 드롭 할 수 있는 공의 아래 요소이다.
ball.hidden = false;
(*)을 호출하기 전에 공을 숨겨야 한다. 그렇지 않으면 공은 보통 포인터 아래의 맨 위 요소로 elemBelow=ball의 좌표를 가진다. 그러므로 공을 숨겼다가 다시 보여주자!
이 코드를 사용하면 언제든지 어떤 요소가 날아가는지 확인할 수 있다. (드롭이 발생했을 때 처리함.)
참고자료