See the Pen Untitled by youngwan2 (@youngwan2) on CodePen.
Drag and Drop API
API는 웹 애플리케이션에서 요소를 드래그하고 다른 위치로 놓는 기능을 구현하는 데 사용된다. 이 API는 기본적인 드래그 앤 드롭 동작을 처리하기 위한 이벤트 및 메서드를 제공하고 있으며, 현재도 계속해서 새로운 기능들이 추가되고 있다.
그래서 오늘은 이 API 를 활용해서 요소 끼리 위치를 바꾸는 간단한 기능을 구현해볼 생각이다.
기본 개념
코드 작성 이전에 우선적으로 필요한 환경 설정과 기본적인 메소드나 기능에 대해 알아 보자.
초기 셋팅 | 필수
우선 각 HTML 요소가 드래그를 가능하게 하려면 각 요소의 속성으로 draggable 을 true 로 지정 해줘야 한다. 이 옵션을 지정해주지 않으면, 향후 스크립트에서 drag 관련 이벤트가 적용되지 않는다.
<div draggable="true">드래그 가능한 요소</div>
이벤트 리스너
Drag and Drop API 를 사용하기 위해서는 기본적으로 제공하는 이벤트 리스너를 등록해야 하는데, 지금 부터 살펴볼 이벤트는 향후 기능 구현에 사용되는 주요 이벤트이다(이 외에도 추가적인 이벤트가 있지만, 사용하지 않는 것은 과감하게 배제 시켰다.)
dragstart | 드래그 시 딱 한 번 호출
dragstart 는 draggable 이 true 인 요소를 드래그하는 시점에 한 번 호출되는 이벤트이다. 해당 이벤트에서 생성되는 event 객체는 드래그된 요소에 대한 정보를 가지고 있다.
draggableElement.addEventListener('dragstart', handleDragStart);
dragover | 드래그 동안 계속 호출
dragover 는 드래그 대상 요소를 드래그 하는 동안 지속적으로 호출되는 이벤트이다. 이 이벤트가 호출 될 때 event 객체에 표시되는 요소는 드래그 되는 요소가 지나가는 위치에 있는 요소가 된다.
draggableElement.addEventListener('dragover', handleDragOver);
drop | 타겟 요소가 있는 지점에 드롭된 직후 한 번 호출
drop 는 드래그 후에 마우스를 놓으면 즉, 요소가 드롭되는 시점에 한 번 호출되는 이벤트이다.
draggableElement.addEventListener('drop', handleDragEnd);
dataTransfer | 데이터 전송과 관련한 다양한 프로퍼티나 메서드를 보유하고 있는 객체
dragstart 이벤트에서 호출 시 아이템 드롭에 필요한 데이터를 전송하기 위한 다양한 기능을 제공하고 있는 이벤트 객체의 프로퍼티이다
무수히 많은 속성이 있고, 지금도 계속해서 추가되고 있으나, 오늘 만들고자 하는 기능에 필요한 속성은 딱 두가지 이므로 이에 대해서만 알아 보고 넘어갈 것이다.
dataTransfer.setData(데이터타입, 데이터) | 데이터 임시 저장 메서드
setData() 메서드는 드래그 이벤트를 호출할 때 클립보드에 임시로 저장할 데이터를 저장하기 위한 메서드이다.
첫 번째 인자로는 데이터 타입을 명시하는데, 일반적으로 text 데이터를 저장하는 경우에는 'text/plain' 이라고 지정하는 것을 권장한다. 이러한 데이터 형식을 MIME 이라 부르는데, 다양한 데이터의 유형을 식별하기 위한 표준이라고 한다. 이에 대한 상세한 예시는 하단에 정리해 두었다.
dataTransfer.getData(데이터타입) | 임시 저장된 데이터를 불러오는 메서드
getData() 메서드는 앞서 setData 로 클립보드에 저장한 데이터를 가져오는 역할을 한다.
주의사항
이벤트의 기본 동작 막기 | 중요
드래그 앤 드롭 이벤트 핸들러에서 기본 동작을 막아야 한다. 예를 들어, dragover와 drop 이벤트의 경우 브라우저의 기본동작을 막지 않으면, 두 이벤트가 정상적으로 호출되지 않을 수 있다. 따라서 event.preventDefault()를 호출하여 브라우저의 기본 동작을 방지해야 한다.
드래그 가능한 요소와 드롭 대상 구분 | 상식
당연한 말이지만, 드래그 가능한 요소와 드롭 대상을 올바르게 식별해야 한다. 요소 에 draggable 속성을 설정하고, 드롭 대상에는 dragover (드로그 하여 움직이는) 이벤트를 통해 드롭을 허용하는지 확인한다.
Cross-browser 호환성 고려 | 중요
드래그 앤 드롭은 브라우저에 따라 다르게 동작할 수 있으므로 cross-browser 호환성을 고려해야 합니다. 물론 최신 브라우저는 큰 차이점이 없는 경우가 대부분이지만, 그래도 조심해야 한다.
실습
HTML
드래그 이벤트를 적용하기 위해서는 타겟이 되는 요소의 속성으로 draggable 을 true로 지정해야 하므로 다음과 같이 옵션을 할당한다.
<article class="container">
<div class="card" draggable="true">0</div>
<div class="card" draggable="true">1</div>
<div class="card" draggable="true">2</div>
<div class="card" draggable="true">3</div>
<div class="card" draggable="true">4</div>
<div class="card" draggable="true">5</div>
</article>
CSS
css 는 draggable 속성이 부여된 부모 요소에 flex 를 지정하여, 각 자식요소가 정렬되도록 지정했다. 그 외 특별한 내용은 없으므로 넘어간다.
참고로 최신 브라우저의 CSS 는 SCSS(SASS) 처럼 중첩으로 선택자를 지정할 수 있게 되었다.
.container {
margin: 0 auto;
display: flex;
flex-wrap: wrap;
max-width: 900px;
.card {
transition: 1s;
margin: 2px;
width: 100px;
background-color: green;
height: 80px;
font-size: 2em;
}
}
JS
1단계) 드래그 이벤트 정의 및 함수 정의
우선, dragstart, dargover, drop 이벤트를 등록하는 한다. 각 이벤트는 앞서 설명한 대로 드래그 시작 시 호출되는 이벤트, 드래그 중에 호출되는 이벤트, 드래그가 종료되는 시점에 호출되는 이벤트이다.
그리고 각 이벤트 객체를 전달받아 기능을 구현할 각각의 함수를 정의한다. 함수의 이름은 각 이벤트와 매칭이 되도록 dragStart(), dragOver(), dragDrop() 순으로 지정했다.
여기 까지 작성했다면, 다음과 같은 코드가 작성되었을 것이다.
function dragStart(e){}
function dragOver(e){}
function dragDrop(e){}
container.addEventListener('dragstart',dragStart)
container.addEventListener('dragover',dragOver)
container.addEventListener('drop', dragDrop)
2단계) dragStart 함수 구현
2단게는 dragStart 함수를 구현하는 것이다. dragStart( ) 는 요소가 드래그 되는 순간에 한 번 실행되는 함수로서, 드래그된 요소를 식별하고, 해당 식별 정보를 임시로 저장하는 용도로 활용된다. 그리고 이렇게 저장된 정보는 향후 drop 된 요소와 비교하여 위치를 바꾸는 값으로 활용된다.
const container = document.querySelector('.container')
function dragStart(e){
// 1. 드래그 할 아이템을 items 변수에 담는다.
// .children 으로 반환되는 리스트는 유사배열이므로
// 전개 연산자(...) 을 이용해서 기존 참조를 벗기고, 일반 자바스크립트 배열 형태로 변환시킨다.
const items = [...e.target.parentNode.children]
// 2. drag 를 시작하는 아이템을 담는다.
// 그리고 해당 아이템이 items 배열에서 어느 위치에 있는지 인덱스를 구해서 담는다.
const currentItem = e.target
const currentItemIndex = items.indexOf(currentItem)
// 3. index 라는 키에 담아서 dataTransfer 객체에 임시 저장한다.
e.dataTransfer.setData('index', currentItemIndex)
}
container.addEventListener('dragstart',dragStart)
3단계) dragover() 함수 구현하기
이 함수는 기능 구현에서 특별한 역할을 별도로 수행하지는 않는다. 그저 브라우저의 기본동작으로 인해 draggable 요소가 식별되지 못하는 문제를 막기 위해 e.preventDefault() 를 적용하는 것이 다이다.
function dragOver(e){
e.preventDefault()
}
container.addEventListener('dragover',dragOver)
4단계) dragDrop() 함수 구현하기
dragDrop() 함수는 앞서 dragStart 함수로 선택한 요소를 dragOver 를 통해 이동 시킨 후, 이동 시킨 요소와 drop 되는 지점에 있는 요소의 자리를 바꾸는 역할을 수행한다.
function dragDrop(e){
// 1. 브라우저 기본 동작을 막는다.
e.preventDefault();
// 2. 타겟이 되는 요소가 이닌 경우를 방지하기 위해 만일 부모 요소에 drop 되는 경우 탈출한다.
if(e.target.classList.contains('container')) {
return
}
// 3. 타겟요소(draggable이 지정된 요소)들을 자바스크립트 배열로 만들어 items 변수에 담는다.
const items = [...e.target.parentNode.children]
// 4. dragStart() 함수에서 setData로 저장한 데이터(타겟요소의 인덱스)를 불러온다.
// 5. 자식 요소 중에서 drop 할 요소를 찾아서 dropItem 변수에 담는다.
const dropItemIndex = e.dataTransfer.getData('index')
const dropItem =items[dropItemIndex]
// 6. e.target : drop 되는 지점에 있는 요소를 가라킨다.
// 7. items 중에 e.target 의 인덱스를 구한다.
const currentItem = e.target
const currentItemIndex = items.indexOf(currentItem)
// 8. 드롭할 아이템의 인덱스가 드롭되는 지점의 아이템 인덱스 보다 큰 경우
// 드롭되는 지점의 요소 앞으로 드롭할 아이템을 이동시킨다.
if(dropItemIndex>currentItemIndex) {
currentItem.before(dropItem)
}
// 9. 8 의 반대라면
// 드롭되는 지점의 요소 뒤로 드롭할 아이템을 이동시킨다.
if(dropItemIndex<currentItemIndex) {
currentItem.after(dropItem)
}
}
container.addEventListener('drop', dragDrop)
최종 결과물
참고로 해당 드래그 요소를 위 최종결과물 제목을 벗어난 위치로 드래그 하면 오염이 발생한다.
MIME 데이터 형식의 유형 (기타)
텍스트 데이터
- text/plain: 일반 텍스트 데이터
- text/html: HTML 문서
- text/css: CSS 스타일시트
이미지 데이터
- image/jpeg: JPEG 이미지
- image/png: PNG 이미지
- image/gif: GIF 이미지
오디오 데이터
- audio/mpeg: MP3 오디오
- audio/wav: WAV 오디오
- audio/midi: MIDI 오디오
비디오 데이터
- video/mp4: MP4 비디오
- video/quicktime: QuickTime 비디오
- video/webm: WebM 비디오
애플리케이션 데이터
- application/json: JSON 데이터
- application/xml: XML 데이터
- application/pdf: PDF 문서
'자바스크립트' 카테고리의 다른 글
[javascript] HTML5 Web Speech API 로 TTS 만들기 (0) | 2024.01.15 |
---|---|
[javascript] 3d 스틸 박스 (작성중) (0) | 2024.01.11 |
[javascript] with | 타겟을 지정한 값으로 수정한 새로운 배열을 반환하는 메소드 (1) | 2023.12.12 |
[javascript] copyWithin(target, start, [end]) | 같은 배열에서 특정 요소들을 복사하여 배열의 일부에 덮어씌운 새로운 배열을 반환하는 메소드 (0) | 2023.12.11 |
[javascript] reversed, toReversed | 배열의 순서를 뒤짚는 메서드 (0) | 2023.12.11 |