...
HTML 드래그 앤 드롭 사용법
드래그(drag)와 드롭(drop)은 컴퓨터를 이용하면서 정말 많이 사용하는 기능 중에 하나일 것이다. 파일 애플리케이션에서 문서를 복사해 이동하는 것 부터 주문 하려는 물건을 장바구니에 드롭하는 것 까지 일상생활에서 많이 접해 봤을 것이다.
HTML 그리고 JavaScript에서의 드래그 드롭은 이벤트 기반으로 작동하게 되는데, 마우스 커서로 객체(object)를 드래그해서 놓을 때까지 여러 단계의 이벤트가 순차적으로 발생하게 되어 동작이 완료되게 된다.
이번 포스팅에서는 자바스크립트를 이용해 드래그 앤 드롭 이벤트 동작 원리를 이해하고 여러 예제를 구현하는 시간을 가져볼 예정이다.
드래그 & 드롭 이벤트 종류
HTML에서 요소가 드래그 이벤트가 발생 할 수 있도록 해당 요소의 속성으로 draggable="true" 값을 주면 요소와 상호동작 할때마다 아래와 같은 드래그 드롭 이벤트들이 발생하게 된다.
<div draggable="true" class="item">
<p>드래그용 아이템</p>
</div>
태그 중에 디폴트로 draggable한 요소가 몇가지 있는데, 예를 들어 <a>는 가본적으로 드래그 가능하고, <span>은 드래그 불가능하다. (직접 링크를 드래그 해보자)
다음은 드래그 앤 드롭시 일어나는 이벤트를 순서대로 나열한 표이다. 물론 반드시 이 이벤트들을 모두 따를 필요는 없고 필요한 것만 가져가서 구현하면 된다.
단, 이중 drop, dragover 이벤트는 필수로 사용해야 하는 이벤트 이다. dragover 이벤트를 적용하지 않으면 drop 이벤트가 작동하지 않으니까 말이다.
이벤트 순서 ↓ | 설명 |
dragstart | 1. 사용자가 객체(object)를 드래그하려고 시작할 때 발생함. |
drag | 2. 대상 객체를 드래그하면서 마우스를 움직일 때 발생함. |
dragenter | 3. 마우스가 대상 객체의 위로 처음 진입할 때 발생함. |
dragover | 4. 드래그하면서 마우스가 대상 객체의 영역 위에 자리 잡고 있을 때 발생함. |
drop | 5. 드래그가 끝나서 드래그하던 객체를 놓는 장소에 위치한 객체에서 발생함. 리스너는 드래그된 데이터를 가져와서 드롭 위치에 놓는 역할을 함 |
dragleave | 6. 드래그가 끝나서 마우스가 대상 객체의 위에서 벗어날 때 발생함. |
dragend | 7. 대상 객체를 드래그하다가 마우스 버튼을 놓는 순간 발생함. |
기본적으로 HTML 요소는 다른 요소의 위에 위치할 수 없다. 따라서 다른 요소 위에 위치할 수 있도록 만들기 위해서는 놓일 장소에 있는 요소의 기본 동작을 막아야만 한다. 이 작업을event.preventDefault() 메소드를 호출하는 것만으로 간단히 설정할 수 있다.
dragstart 이벤트
- 드래그가 시작되는 순간 발생
const item = document.querySelector(".item");
item.addEventListener("dragstart", (e) => {
console.log(e);
console.log("드래그를 시작하면 발생하는 이벤트");
});
See the Pen drag-drop-연습-dragstart by barzz12 (@inpaSkyrim) on CodePen.
drag 이벤트
- 요소를 드래그할 때 발생
const item = document.querySelector(".item");
item.addEventListener("drag", (e) => {
console.log(e);
console.log("드래그하면 발생하는 이벤트");
});
See the Pen drag-drop-연습-1 by barzz12 (@inpaSkyrim) on CodePen.
dragenter 이벤트
- 해당 이벤트를 지정한 요소에 드래그한 아이템이 들어가면 발생
- 드래그하는 요소가 dragenter 이벤트를 달아놓은 요소 안에 진입했을 때 발생
// 드래그 요소가 이동하여 위치할 우측 박스 영역
const container2 = document.querySelector(".container2");
container2.addEventListener("dragenter", (e) => {
console.log(e);
console.log("드래그 요소가 이 영역에 닿으면 발생하는 이벤트");
});
See the Pen drag-drop-연습-3 by barzz12 (@inpaSkyrim) on CodePen.
dragover 이벤트
- 이 이벤트가 달린 영역 위에 드래그 요소가 있으면 발생
// 드래그 요소가 이동하여 위치할 우측 박스 영역
const container2 = document.querySelector(".container2");
container2.addEventListener("dragover", (e) => {
console.log(e);
console.log("드래그 요소가 이 영역에 위에 계속 위치하면 발생하는 이벤트");
});
See the Pen drag-drop-연습-dragover by barzz12 (@inpaSkyrim) on CodePen.
dragleave 이벤트
- dragexit 이벤트 대신 사용.
- 어떤 요소든 드래그되고 있다면 이 이벤트가 달린 요소에 들어갔다가 나가는 시점에 발생하는 이벤트
- dragenter 이벤트와 동작이 겹칠수 있기 때문에
e.preventDefault()로 제한하며 둘이 결합하여 사용함
// 처음 드래그 요소가 위치하고 있는 좌측 박스 영역
const container = document.querySelector(".container");
container.addEventListener("dragenter", (e) => {
e.preventDefault();
console.log(e);
console.log("드래그 요소가 '첫' 번째 박스 영역에 최초로 진입했을 때");
});
container.addEventListener("dragleave", (e) => {
e.preventDefault();
console.log(e);
console.log("드래그 요소가 '첫' 번째 박스 영역을 떠나면 발생하는 이벤트");
});
// 드래그 요소가 이동하여 위치할 우측 박스 영역
const container2 = document.querySelector(".container2");
container2.addEventListener("dragenter", (e) => {
e.preventDefault();
console.log(e);
console.log("드래그 요소가 '두' 번째 박스 영역에 최초로 진입했을 때");
});
container2.addEventListener("dragleave", (e) => {
e.preventDefault();
console.log(e);
console.log("드래그 요소가 '두' 번째 박스 영역을 떠나면 발생하는 이벤트");
});
See the Pen drag-drop-연습-dragleave by barzz12 (@inpaSkyrim) on CodePen.
drop 이벤트
- 이 이벤트가 달린 요소에 드래그를 끝내면 발생 (dragover랑 같이 써야함)
- drop 이벤트 역시 드롭될 요소에는
e.preventDefault()를 사용하지 않으면 정상적인 동작이 되지 않을 수 있으므로
이벤트에 preventDefault() 코드를 작성하는 것이 좋다. - 단독으로 사용했을 때는 동작을 하지 않았고 dragover이벤트와 함께 사용했을 때 비로소 동작이 된다.
// 처음 드래그 요소가 위치하고 있는 좌측 박스 영역
const container = document.querySelector(".container");
container.addEventListener("dragover", (e) => {
e.preventDefault();
//console.log("드래그 요소가 '첫' 번째 박스 영역에 계속 위치하면 발생하는 이벤트");
});
container.addEventListener("drop", (e) => {
e.preventDefault();
console.log("드래그 요소가 '첫' 번째 박스 영역에 드롭");
});
// 드래그 요소가 이동하여 위치할 우측 박스 영역
const container2 = document.querySelector(".container2");
container2.addEventListener("dragover", (e) => {
e.preventDefault();
//console.log("드래그 요소가 '두' 번째 박스 영역에 계속 위치하면 발생하는 이벤트");
});
container2.addEventListener("drop", (e) => {
e.preventDefault();
console.log("드래그 요소가 '두' 번째 박스 영역에 드롭");
});
See the Pen drag-drop-연습-drop by barzz12 (@inpaSkyrim) on CodePen.
dragend 이벤트
- 요소의 드래그가 끝날때 발생(드래그 도중에 마우스 버튼을 아무데나 놓으면)
const item = document.querySelector(".item");
item.addEventListener("dragend", (e) => {
console.log(e)
console.log("드래그가 끝나면 발생하는 이벤트");
});
See the Pen drag-drop-연습-dragend by barzz12 (@inpaSkyrim) on CodePen.
DataTransfer 객체
드래그 앤 드롭 이벤트를 위한 모든 이벤트 리스너 메소드(event listener method)는 DataTransfer 객체를 반환한다.
이렇게 반환된 DataTransfer 객체는 드래그 앤 드롭 동작에 관한 정보를 가지고 있다.
item.addEventListener("drag", (event) => {
console.log(event.dataTransfer); // 드래그 이벤트 정보를 담고있는 dataTransfer 객체
});
데이터 전송 기능에 관한 메서드
이 dataTransfer 객체 내에서는 데이터를 저장 및 가져오기, 삭제를 수행할 수 있는 표준 메소드를 가지고 있다.
메소드 | 설명 |
event.dataTransfer.setData(format,data) | 첫번째 매개변수로 포맷 문자열을 지정. 첫번째 매개변수에 지정한 포맷과 일치하는 값을 두번째 매개변수로 지정. 두번째 매개변수로는 문자열만 지정 가능 |
event.dataTransfer.getData(format) | 첫번째 매개변수에 지정한 포맷의 전송 데이터를 반환. 지정된 포맷의 데이터가 지정되어 있지 않으면 공백 문자열을 반환 |
event.dataTransfer.clearData() event.dataTransfer.clearData(format) |
데이터 전송용으로 지정된 데이터를 모두 제거. 첫번째 매개변수로 포맷 문자열을 지정하면 해당 형식과 일치하는 데이터만을 제거. |
event.dataTransfer.types | dragstart 이벤트 발생시 DOM 목록에 있는 data format 을 설정하며 setData 함수를 호출할때 지정되는 format 문자열을 배열형식으로 얻을 수 있다. |
만일 파일을 드래그 드롭 하는 것이라면, dataTrasfer에 files 프로퍼티로 접근하여 파일 정보를 가져올 수 있다
// 드래그한 파일 객체가 해당 영역에 놓였을 때
요소.ondrop = (e) => {
e.preventDefault();
// 드롭된 파일 리스트 가져오기
const files = [...e.dataTransfer?.files];
console.log(files);
// 파일 리스트 띄위기
요소.innerHTML = files.map(file => file.name).join("<br>");
}
See the Pen drag-drop-완성예제2-파일 by barzz12 (@inpaSkyrim) on CodePen.
커스텀 드래그 고스트 이미지
요소를 마우스로 드래그하면 그 요소의 모습이 고스트 이미지(ghost image) 로서 커서에 딸려 나온다. 이 드래그 이미지를 DataTransfer.setDragImage() 메서드를 통해 사용자 커스텀이 가능하다.
document.getElementById("drag-with-image").addEventListener("dragstart", function(e) {
cosnt img = new Image();
img.src = "이미지 경로";
// setDragImage(이미지요소, xOffset, yOffset)
e.dataTransfer.setDragImage(img, 150, 150); // 사용할 사용자 정의 이미지를 설정
}, false);
See the Pen Custom Img 드래그 드롭 by barzz12 (@inpaSkyrim) on CodePen.
드래그 앤 드롭 예제 코드 모음
기본 예제
- dragstart 이벤트핸들러에서 e.dataTransfer.setData로 전달할 데이터를 지정해준다.
- setData는 키-값 형식으로 저장하기 때문에, 키만 다르면 여러 개의 데이터를 저장할 수 있다.
- dragover이벤트에서는 e.preventDefault()로 drop 이벤트가 호출될 수 있게 해주고
- drop 이벤트에서 e.dataTransfer.getData로 데이터를 받을 수 있다.
<p id="drag" draggable="true">드래그해보세요.</p>
<br>
<div id="drop">여기에 드롭하세요</div>
<script>
document.getElementById('drag').ondragstart = function() {
e.dataTransfer.setData('data', this.innerHTML); // 드래그해보세요 문자열 전달
};
document.getElementById('drop').ondragover = function(e) {
e.preventDefault(); // 필수 이 부분이 없으면 ondrop 이벤트가 발생하지 않습니다.
};
document.getElementById('drop').ondrop = function() {
alert(e.dataTransfer.getData('data')); // 데이터를 가져옵니다.
};
</script>
See the Pen html13-1 by Hyunyoung Cho (@zerocho) on CodePen.
드래그로 요소 위치 바꾸기
<div class="container">
<button class="draggable" draggable="true">🦊</button>
<button class="draggable" draggable="true">🐸</button>
</div>
<div class="container">
<button class="draggable" draggable="true">🐶</button>
<button class="draggable" draggable="true">🐱</button>
</div>
<script>
const draggables = document.querySelectorAll(".draggable");
const containers = document.querySelectorAll(".container");
draggables.forEach(draggable => {
draggable.addEventListener("dragstart", () => {
draggable.classList.add("dragging");
});
draggable.addEventListener("dragend", () => {
draggable.classList.remove("dragging");
});
});
containers.forEach(container => {
container.addEventListener("dragover", e => {
e.preventDefault();
const afterElement = getDragAfterElement(container, e.clientX);
const draggable = document.querySelector(".dragging");
if (afterElement === undefined) {
container.appendChild(draggable);
} else {
container.insertBefore(draggable, afterElement);
}
});
});
function getDragAfterElement(container, x) {
const draggableElements = [
...container.querySelectorAll(".draggable:not(.dragging)"),
];
return draggableElements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = x - box.left - box.width / 2;
// console.log(offset);
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
},
{ offset: Number.NEGATIVE_INFINITY },
).element;
}
</script>
See the Pen drag-drop-1 by barzz12 (@inpaSkyrim) on CodePen.
<div class="container">
<div class="draggable" draggable="true">
<span class="ico-drag"></span>
<div class="el">HTML</div>
</div>
<div class="draggable" draggable="true">
<span class="ico-drag"></span>
<div class="el">CSS</div>
</div>
<div class="draggable" draggable="true">
<span class="ico-drag"></span>
<div class="el">JavaScript</div>
</div>
</div>
<div class="container">
<div class="draggable" draggable="true">
<span class="ico-drag"></span>
<div class="el">React</div>
</div>
<div class="draggable" draggable="true">
<span class="ico-drag"></span>
<div class="el">Vue</div>
</div>
<div class="draggable" draggable="true">
<span class="ico-drag"></span>
<div class="el">Next JS</div>
</div>
</div>
<script>
/**
* [x] 엘리먼트의 .draggable, .container의 배열로 선택자를 지정합니다.
* [x] draggables를 전체를 루프하면서 dragstart, dragend를 이벤트를 발생시킵니다.
* [x] dragstart, dragend 이벤트를 발생할때 .dragging라는 클래스를 토글시킨다.
* [x] dragover 이벤트가 발생하는 동안 마우스 드래그하고 마지막 위치해놓은 Element를 리턴하는 함수를 만듭니다.
*/
(() => {
const $ = (select) => document.querySelectorAll(select);
const draggables = $('.draggable');
const containers = $('.container');
draggables.forEach(el => {
el.addEventListener('dragstart', () => {
el.classList.add('dragging');
});
el.addEventListener('dragend', () => {
el.classList.remove('dragging')
});
});
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect() //해당 엘리먼트에 top값, height값 담겨져 있는 메소드를 호출해 box변수에 할당
const offset = y - box.top - box.height / 2 //수직 좌표 - top값 - height값 / 2의 연산을 통해서 offset변수에 할당
if (offset < 0 && offset > closest.offset) { // (예외 처리) 0 이하 와, 음의 무한대 사이에 조건
return { offset: offset, element: child } // Element를 리턴
} else {
return closest
}
}, { offset: Number.NEGATIVE_INFINITY }).element
};
containers.forEach(container => {
container.addEventListener('dragover', e => {
e.preventDefault()
const afterElement = getDragAfterElement(container, e.clientY);
const draggable = document.querySelector('.dragging')
// container.appendChild(draggable)
container.insertBefore(draggable, afterElement)
})
});
})();
</script>
See the Pen drag-drop-요소위치 by barzz12 (@inpaSkyrim) on CodePen.
이미지 드래그 드롭
<h1>드래그 앤 드롭을 이용한 객체의 이동</h1>
<p>모나리자 그림을 드래그해서 옆의 사각형으로 옮겨보세요!</p>
<div
ondrop="drop(event)"
ondragover="dragEnter(event)"
>
<img
id="monalisa"
width="180" height="280"
src="/examples/images/img_monalisa.png"
draggable="true"
ondragstart="drag(event)"
>
</div>
<div
ondrop="drop(event)"
ondragover="dragEnter(event)"
>
</div>
<script>
function dragEnter(ev) {
ev.preventDefault();
}
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text"); // img태그 아이디를 가져옴
ev.target.appendChild(document.getElementById(data)); // 다른 div태그에 img를 추가함(옮김. 드래그처리)
}
</script>
See the Pen drag&drop by barzz12 (@inpaSkyrim) on CodePen.
파일 드래그 드롭
<div id="drop">여기에 파일을 드랍하세요</div>
<script>
var drop = document.getElementById('drop');
drop.ondragover = function(e) {
e.preventDefault(); // 이 부분이 없으면 ondrop 이벤트가 발생하지 않습니다.
};
drop.ondrop = function(e) {
e.preventDefault(); // 이 부분이 없으면 파일을 브라우저 실행해버립니다.
var data = e.dataTransfer;
for (var i = 0; i < data.files.length; i++) {
alert(data.files[i].name); // File API 사용
}
};
</script>
See the Pen 파일드랍 by barzz12 (@inpaSkyrim) on CodePen.
<form>
<input type="file" id="file" multiple>
<div class="drop-zone">또는 파일을 여기로 드래그하세요.</div>
</form>
<script>
(function () {
var $file = document.getElementById("file")
var dropZone = document.querySelector(".drop-zone")
var toggleClass = function (className) {
console.log("current event: " + className)
var list = ["dragenter", "dragleave", "dragover", "drop"]
for (var i = 0; i < list.length; i++) {
if (className === list[i]) {
dropZone.classList.add("drop-zone-" + list[i])
} else {
dropZone.classList.remove("drop-zone-" + list[i])
}
}
}
var showFiles = function (files) {
dropZone.innerHTML = ""
for (var i = 0, len = files.length; i < len; i++) {
dropZone.innerHTML += "<p>" + files[i].name + "</p>"
}
}
var selectFile = function (files) {
// input file 영역에 드랍된 파일들로 대체
$file.files = files
showFiles($file.files)
}
$file.addEventListener("change", function (e) {
showFiles(e.target.files)
})
// 드래그한 파일이 최초로 진입했을 때
dropZone.addEventListener("dragenter", function (e) {
e.stopPropagation()
e.preventDefault()
toggleClass("dragenter")
})
// 드래그한 파일이 dropZone 영역을 벗어났을 때
dropZone.addEventListener("dragleave", function (e) {
e.stopPropagation()
e.preventDefault()
toggleClass("dragleave")
})
// 드래그한 파일이 dropZone 영역에 머물러 있을 때
dropZone.addEventListener("dragover", function (e) {
e.stopPropagation()
e.preventDefault()
toggleClass("dragover")
})
// 드래그한 파일이 드랍되었을 때
dropZone.addEventListener("drop", function (e) {
e.preventDefault()
toggleClass("drop")
var files = e.dataTransfer && e.dataTransfer.files
console.log(files)
if (files != null) {
if (files.length < 1) {
alert("폴더 업로드 불가")
return
}
selectFile(files)
} else {
alert("ERROR")
}
})
})();
</script>
See the Pen 파일드래그드롭2 by barzz12 (@inpaSkyrim) on CodePen.
파일 업로드 & 프리뷰
<main class="container">
<label class="label" id="label" for="input">
<div class="inner" id="inner">드래그하거나 클릭해서 업로드</div>
</label>
<input id="input" class="input" accept="image/*" type="file" required="true" multiple="true" hidden="true">
<p class="preview-title">preview</p>
<div class="preview" id="preview"></div>
</main>
<script>
var input = document.getElementById("input");
var initLabel = document.getElementById("label");
input.addEventListener("change", (event) => {
const files = changeEvent(event);
handleUpdate(files);
});
initLabel.addEventListener("mouseover", (event) => {
event.preventDefault();
const label = document.getElementById("label");
label?.classList.add("label--hover");
});
initLabel.addEventListener("mouseout", (event) => {
event.preventDefault();
const label = document.getElementById("label");
label?.classList.remove("label--hover");
});
document.addEventListener("dragenter", (event) => {
event.preventDefault();
console.log("dragenter");
if (event.target.className === "inner") {
event.target.style.background = "#616161";
}
});
document.addEventListener("dragover", (event) => {
console.log("dragover");
event.preventDefault();
});
document.addEventListener("dragleave", (event) => {
event.preventDefault();
console.log("dragleave");
if (event.target.className === "inner") {
event.target.style.background = "#3a3a3a";
}
});
document.addEventListener("drop", (event) => {
event.preventDefault();
console.log("drop");
if (event.target.className === "inner") {
const files = event.dataTransfer?.files;
event.target.style.background = "#3a3a3a";
handleUpdate([...files]);
}
});
function changeEvent(event) {
const { target } = event;
return [...target.files];
};
function handleUpdate(fileList) {
const preview = document.getElementById("preview");
fileList.forEach((file) => {
const reader = new FileReader();
reader.addEventListener("load", (event) => {
const img = el("img", {
className: "embed-img",
src: event.target?.result,
});
const imgContainer = el("div", { className: "container-img" }, img);
preview.append(imgContainer);
});
reader.readAsDataURL(file);
});
};
function el(nodeName, attributes, ...children) {
const node =
nodeName === "fragment"
? document.createDocumentFragment()
: document.createElement(nodeName);
Object.entries(attributes).forEach(([key, value]) => {
if (key === "events") {
Object.entries(value).forEach(([type, listener]) => {
node.addEventListener(type, listener);
});
} else if (key in node) {
try {
node[key] = value;
} catch (err) {
node.setAttribute(key, value);
}
} else {
node.setAttribute(key, value);
}
});
children.forEach((childNode) => {
if (typeof childNode === "string") {
node.appendChild(document.createTextNode(childNode));
} else {
node.appendChild(childNode);
}
});
return node;
}
</script>
See the Pen image-drag-and-drop by an (@YWTechIT) on CodePen.
드래그 객체 이동
<div id="outerContainer">
<div id="container">
<div id="item">
</div>
</div>
</div>
<script>
var dragItem = document.querySelector("#item");
var container = document.querySelector("#container");
var active = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
container.addEventListener("touchstart", dragStart, false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragItem) {
active = true;
}
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
active = false;
}
function drag(e) {
if (active) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, dragItem);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
</script>
See the Pen drag-drop-객체이동 by barzz12 (@inpaSkyrim) on CodePen.
<p>Drag the ball.</p>
<img src="https://en.js.cx/clipart/soccer-gate.svg" id="gate" class="droppable">
<img src="https://en.js.cx/clipart/ball.svg" id="ball">
<script>
// 잠재적 드롭 가능한 요소
let currentDroppable = null;
ball.onmousedown = function (event) {
let shiftX = event.clientX - ball.getBoundingClientRect().left;
let shiftY = event.clientY - ball.getBoundingClientRect().top;
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
document.body.append(ball);
moveAt(event.pageX, event.pageY);
function moveAt(pageX, pageY) {
ball.style.left = pageX - shiftX + 'px';
ball.style.top = pageY - shiftY + 'px';
}
function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
ball.hidden = true;
let elemBelow = document.elementFromPoint(event.clientX, event.clientY); // elemBelow : 드롭할 수 있는 공의 아래 요소, 축구골대
ball.hidden = false;
// 공을 윈도우 밖으로 드래그 했을 때,
// clientX, clientY 가 윈도우 밖에 있으면, elementFromPoint 는 null 반환
if (!elemBelow) return;
// 잠재적으로 드롭할 수 있는 요소를 'droppable' 클래스로 지정한다. 축구 골대
let droppableBelow = elemBelow.closest('.droppable');
// 둘다 null 일 수 있다. 들어오거나, 나가거나에 해당할 때,
// currentDroppale = null : 축구 골대 밖에 있을 때,
// droppableBelow = null : 이벤트 동안 축구 골대 안에 없을 때,
if (currentDroppable != droppableBelow) {
if (currentDroppable) {
// 축구 골대 밖에 있을 때 강조 제거
leaveDroppable(currentDroppable);
}
currentDroppable = droppableBelow;
if (currentDroppable) { // null if we're not coming over a droppable now
// 축구 골대 안으로 들어오는 것을 처리하는 로직
enterDroppable(currentDroppable);
}
}
}
document.addEventListener('mousemove', onMouseMove);
ball.onmouseup = function () {
document.removeEventListener('mousemove', onMouseMove);
ball.onmouseup = null;
};
};
function enterDroppable(elem) {
elem.style.background = 'pink';
}
function leaveDroppable(elem) {
elem.style.background = '';
}
ball.ondragstart = function () {
return false;
};
</script>
See the Pen Untitled by barzz12 (@inpaSkyrim) on CodePen.
드래그 & 드롭 JS 라이브러리
드래그 앤 드롭을 직접 구현하기에는 아직 실력이 부족하다면 라이브러리를 이용하는 것도 하나의 방법이다.
대표적인 라이브러리 두가지를 소개해본다.
드래그 드롭 정렬 - Sortable.js
Sortable은 기본 HTML5 드래그 앤 드롭 API를 사용하여 객체 목록을 정렬하는 가볍고 간단한 모듈이다.
모든 최신 브라우저 및 터치 장치와 호환 된다.
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js"></script>
const columns = document.querySelectorAll(".column");
columns.forEach((column) => {
new Sortable(column, {
group: "shared",
animation: 150,
ghostClass: "blue-background-class"
});
});
See the Pen drag-drop-완성예제3-sortable by barzz12 (@inpaSkyrim) on CodePen.
파일/이미지 드래그 드롭 - Dropzone.js
고퀄리티의 드래그드롭 메뉴를 간단하게 구현할수있는 라이브러리이다.
사용법에 대해선 다음 포스팅을 참고 바란다.
See the Pen dropzone.js sample-1 by barzz12 (@inpaSkyrim) on CodePen.
# 참고자료
https://ko.javascript.info/mouse-drag-and-drop
https://www.zerocho.com/category/HTML&DOM/post/5942c4ed858a010018a8c32f
https://ezerror.com/ko/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD
https://gingerkang.tistory.com/127
https://gurtn.tistory.com/143
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.