본문 바로가기

자바스크립트

[Javascript] HTML5 dragable API 를 사용하여 요소의 위치를 바꾸어 보자

반응형

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)

 

최종 결과물

참고로 해당 드래그 요소를 위 최종결과물 제목을 벗어난 위치로 드래그 하면 오염이 발생한다.  

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

 

 

 

 

 


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 문서

 

 

반응형