드래그는 피그마 같은 디자인 툴 사이트에 매우 많이 사용된다.
또한, 웹 사이트에 파일을 업로드 할 때, 드래그 앤 드랍으로 파일을 업로드 한 경험이
대부분 있을 것이다.
하지만, 단순하게 보였던 드래그는 생각보다 다루기 어려운 이벤트이다.
DOM에서 제공하는 드래그 관련 이벤트도 다양하며,
제대로 활용하기 위해서는 뷰포트 내 좌표에 대한 개념도 알아야 한다.
어려운 것은 사실이지만, 드래그 기능은 현대 웹 애플리케이션에서 중요한 역할을 하며,
사용자 경험을 크게 향상시킬 수 있다.
이번 글에서는 drag 관련 이벤트를 정리해보겠다.
1️⃣ drag 이벤트 종류
드래그 이벤트는 총 7종류가 있지만, 크게 두 분류로 나눌 수 있다.
- 드래그 되는 요소에서 발생하는 이벤트
- dragstart
- drag
- dragend
- 드롭 대상(또는 잠재석 드롭 대상)에서 발생하는 이벤트
- dragenter
- dragover
- dragleave
- drop
두 분류의 차이를 명확히 이해하는 것이 중요하다.
1의 경우는 마우스를 갖다대고 잡아서 움직이는 요소에서 발생하는 이벤트이다.
2의 경우는 드래그로 가져온 요소와 상호작용 할 때 발생하는 이벤트이다.
아래 예시들을 확인하면 둘의 차이에 대한 감이 올 것이다.
2️⃣ 드래그 되는 요소에서 발생하는 이벤트
지금부터 아래 이벤트들은 모두 드래그 되는 요소에서 발생하는 이벤트임을 명심하자.
✅ dragstart
dragstart 는 이름 그대로 드래그가 시작될 때 발생하는 이벤트이다.
이때, 드래그가 시작되자마자 딱 한 번만 발생한다.
아래 예시에서 버튼을 드래그 해보면 바로 감이 온다.
See the Pen dragstart by 정준희 (@eisdsfpi-the-animator) on CodePen.
위의 버튼을 드래그 하면 dragstart 이벤트가 발생하여, 등록한 핸들러가 작동된다.
이때, 기본적으로 브라우저는 드래그를 막기 때문에, 드래그할 요소는 HTML 태그의 속성으로
draggable = true 를 명시해주어야 한다.
어떠한 원리인지 JS 코드를 자세히 들여다보자.
// 텍스트를 화면에 출력해주는 용도
function printText(text) {
const resultDiv = document.querySelector(".result");
const pEl = document.createElement("p");
pEl.textContent = text;
resultDiv.append(pEl);
}
// 각 버튼에 dragstart 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragstart", () => {
printText(`${index + 1} 버튼에 dragstart 이벤트 발생`);
});
});
querySelectorAll API를 이용하여 모든 버튼을 찾은 다음, 버튼에 핸들러를 등록하였다.
드래그가 시작되면 핸들러가 작동하여, printText 함수를 통해
몇 번 버튼에서 발생했는지 화면에 나타내준다.
✅ drag
drag 이벤트는 어떤 요소가 드래그 되며 계속 움직일 때 지속적으로 발생하는 이벤트이다.
마우스 포인터의 위치를 추적하며, 포인터가 바뀔 때마다 이벤트가 발생된다.
즉, 조금만 움직여도 엄청나게 이벤트가 발생한다.
아래 예시를 확인하자.
See the Pen Untitled by 정준희 (@eisdsfpi-the-animator) on CodePen.
// 각 버튼에 drag 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("drag", () => {
printText(`${index + 1} 버튼에 drag 이벤트 발생`);
});
});
drag 이벤트는 매우 빈번하게 발생되기 때문에 성능 이슈를 야기할 수 있다.
따라서, 핸들러 내 무거운 작업이 있다면 성능 저하가 발생할 수 있으므로
최대한, 가벼운 작업만 수행하도록 설계하는 것이 좋다.
✅ dragend
dragend 역시 이름 그대로 드래그가 끝났을 때, 발생하는 이벤트이다.
즉, 드래그 하던 요소를 화면에 놓았을 때 발생하는 이벤트이다.
아래 예시를 확인하자.
dragover 핸들러 코드는 삭제하고, dragstart만 남겨두었다.
See the Pen Untitled by 정준희 (@eisdsfpi-the-animator) on CodePen.
// 각 버튼에 dragend 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragend", () => {
printText(`${index + 1} 버튼에 dragend 이벤트 발생`);
});
});
3️⃣ 드롭 대상(또는 잠재석 드롭 대상)에서 발생하는 이벤트
이제부터 나올 이벤트들은 드래그를 하는 요소에서 발생하는 이벤트가 아니다.
어떠한 요소가 드래그 되었을 때, 해당 드래그 된 요소가
나한테 들어오거나, 나가거나, 내 위에 계속 머무르거나, 나한테 드래그 된 요소를 떨어트리거나
할 때 발생하는 이벤트들이다.
즉, 설명이 장황하지만 드래그 된 요소와 상호작용을 하기 위하여 발생하는 이벤트라는 것이다.
하나씩 확인하자.
✅ dragenter
dragenter 는 이름 그대로, 어떤 드래그 된 요소가 나의 영역에 들어왔을 때 발생하는 이벤트이다.
이때, dragstart 처럼 들어오고 딱 한 번만 발생하는 이벤트이다.
아래 예시를 확인하자.
See the Pen Untitled by 정준희 (@eisdsfpi-the-animator) on CodePen.
JS 코드를 자세히 분석해보자.
// 텍스트를 화면에 출력해주는 용도
function printText(text) {
const resultDiv = document.querySelector(".result");
const pEl = document.createElement("p");
pEl.textContent = text;
resultDiv.append(pEl);
}
let draggedButton = null;
// 각 버튼에 dragstart 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragstart", (e) => {
draggedButton = e.target;
});
});
// 각 버튼에 dragenter 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragenter", (e) => {
if (draggedButton !== e.target) {
printText(`${index + 1} 버튼에 드래그 된 요소 진입`);
}
});
});
드래그가 시작되면(dragstart) draggedButton 에 해당 요소를 할당해준다.
각 버튼에 dragenter 이벤트 핸들러를 등록하여 어떤 드래그된 요소가 내 영역에 들어왔을 때,
printText 함수를 실행시켜주는 로직이다.
이때, 내 영역에 들어온 드래그 된 버튼은 당연히 나와 다를텐데 왜 if문을 사용했는지 의문이 들 수 있다.
현재 모든 버튼에 dragenter 핸들러를 등록하였기 때문에,
드래그 할 버튼에도 dragenter 핸들러가 등록이 되어있다.
따라서, 위의 if문이 없으면 어떤 버튼을 드래그 하자마자, 그 버튼의 dragenter 핸들러도
곧바로 작동되어 printText 함수가 출력된다.
오류는 없지만, drag할 요소에 dragenter 핸들러가 동작하는 것은 논리적으로 맞지 않기에
위의 if문이 필요한 것이다.
만약, if문이 없다면 아래와 같이 작동한다.
드래그를 하는 요소도 dragenter 핸들러가 작동된 것을 확인할 수 있다.
See the Pen dragenter by 정준희 (@eisdsfpi-the-animator) on CodePen.
✅ dragover
dragover는 드래그 된 요소가 내 영역 위에서 움직일 때 지속적으로 발생하는 이벤트이다.
마치, drag 이벤트와 유사하지만 주요한 차이점은
dragover 이벤트는 드래그 되는 요소에서 발생하는 이벤트가 아니란 점이다.
See the Pen Untitled by 정준희 (@eisdsfpi-the-animator) on CodePen.
// 각 버튼에 dragenter 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragover", (e) => {
if (draggedButton !== e.target) {
printText(`${index + 1} 버튼에 드래그 된 요소 진입`);
}
});
});
✅ dragleave
이름 그대로 드래그 된 요소가 내 영역을 벗어났을 때 한 번만 실행되는 이벤트이다.
See the Pen dragleave by 정준희 (@eisdsfpi-the-animator) on CodePen.
// 각 버튼에 dragleave 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragleave", (e) => {
if (draggedButton !== e.target) {
printText(`${index + 1} 버튼에 드래그 된 요소 진입 후 벗어남`);
}
});
});
✅ drop
어찌보면 drag & drop 에서 핵심 이벤트이다.
이름 그대로 드래그 된 요소를 내 영역에 떨어트렸을 때 즉, drop 했을 때 발생하는 이벤트이다.
그런데 일반적인 이벤트처럼 drop 이벤트 핸들러를 등록하면 작동하지 않는다.
See the Pen Untitled by 정준희 (@eisdsfpi-the-animator) on CodePen.
// 각 버튼에 drop 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("drop", (e) => {
if (draggedButton !== e.target) {
printText(`${index + 1} 버튼에 DROP !!`);
}
});
});
드래그 한 요소를 다른 버튼에 드롭을 해도 아무런 변화가 없는 것을 확인할 수 있다.
왜 이러한 현상이 발생할까 ?
그 이유는 브라우저가 대부분 요소에서 드롭 하는 것을 기본적으로 금지하고 있기 때문이다.
따라서, 우리는 금지하는 것을 막지 않았기에 드롭 이벤트가 작동되지 않은 것이다.
조금 더 자세히 들어가면, 우리는 드롭 할 대상 위로 dragover 하면서 드래그된 요소를 갖다 놓는다.
이때, dragover의 기본 동작은 "드롭을 허용하지 않는 것"이다.
따라서, drop을 하기 위해서는 dragover 핸들러에서 기본 동작을 막아주는
e.preventDefault() 메소드를 호출해야 한다.
그 결과로 "드롭을 허용하지 않는 것"을 막아주었기 때문에, 드롭이 허용되는 것이다.
한번 dragover 핸들러를 추가하고, e.preventDefault() 를 핸들러 내에 추가해보겠다.
See the Pen Untitled by 정준희 (@eisdsfpi-the-animator) on CodePen.
// 각 버튼에 dragover 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("dragover", (e) => {
e.preventDefault()
});
});
// 각 버튼에 drop 이벤트 관련 핸들러 등록
document.querySelectorAll("button").forEach((button, index) => {
button.addEventListener("drop", (e) => {
e.preventDefault();
if (draggedButton !== e.target) {
printText(`${index + 1} 버튼에 DROP !!`);
}
});
});
이제 드롭 이벤트가 작동되는 것을 확인할 수 있다.
그렇다면, 도대체 왜 브라우저는 이렇게 헷갈리게 설계가 된 것일까 ?
이유는 다음과 같다.
- 의도하지 않은 드래그 앤 드롭 동작 방지
- 개발자가 드롭을 허용할 요소를 명시적으로 지정하여 명확하게 제어할 수 있게 함.
- 필요한 곳에서만 드롭 이벤트를 처리함으로써, 불필요한 이벤트 처리를 줄일 수 있음.
이러한 설계를 통해 웹의 안전성, 보안성, 명확성을 높일 수 있는 것이다.
이번 글을 정리해보겠다.
- drag 관련 이벤트는 총 7가지가 있다.
- drag 관련 이벤트는 drag의 주체에 따라 총 두 가지로 분류된다.
- drop 핸들러를 등록하기 위해서는 반드시 dragover 핸들러에서 e.preventDefault() 메소드를 호출해야한다.
'JavaScript' 카테고리의 다른 글
[JavaScript] Promise 정적 메소드 분석 (1) | 2024.09.15 |
---|---|
[JavaScript] Promise와 비동기(Asnycronous) 처리 (0) | 2024.09.14 |
[JavaScript] Element.closest 메소드 분석 (0) | 2024.08.31 |
[JavaScript] 이벤트 버블링(Bubbling), 이벤트 위임(Delegation) (2) | 2024.08.31 |
[JavaScript] 모듈 시스템 CommonJS vs ES6 비교 (0) | 2024.08.25 |