drag and drop API

YEONGHUN KO·2022년 1월 12일
1

JAVASCRIPT - BASICS

목록 보기
17/27
post-thumbnail

to do list component를 진행하다가 drag and drop에서 제대로 공부해보게 되었다. 예전에 to do list 만들때는 j-query를 이용해 구현했는데 이번에는 바닐라로 만들어보고 싶어서 파기 시작했고 나름 재밌고 유용한 기술이라 생각해서 article을 적는다.

drag and drop이 무엇이냐?

  • 이름 그대로 특정 dom이나 파일을 끌어다가 어딘가로 옮길때 쓰이는 이벤트이다. 나같은 경우는 list의 순서를 바꾸기 위해 사용했다.

drag 이벤트

  • addEventListener를 통해서 등록한다. 아주 세밀하게 동작을 나눌 수 있다. drag가 시작되는 시점 > 드래그 중 > 드래그가 어떤 태그로 들어왔을 때 > 나갈 때 > 잡고 있던 태그를 놓을 때 > 드래그가 끝남.

요런 식으로 드래그 이벤트가 흘러간다고 보면 된다. 그리고, 일반적인 camalCase와는 달리 이벤트 등록은 모두 소문자로 기입한다.

미니프로젝트를 통해 본격적으로 설명

drag drop event를 통해 list를 옮길 방법을 찾던중 괜찮은 템플릿을 발견해서 그걸 기반으로 설명을 진행해보려 한다.

html 구조

우선 html 구조부터

<div class="list">
  <input type="text" class="input" placeholder="Add items in your list" />
  <span class="add">+</span>
</div>
<ul>
  <li class="draggable" draggable="true">HTML</li>
  <li class="draggable" draggable="true">CSS</li>
  <li class="draggable" draggable="true">JavaScript</li>
  <li class="draggable" draggable="true">PHP</li>
  <li class="draggable" draggable="true">MySQL</li>
</ul>

input에 입력하면 ul에 li가 쌓이는 식이다. 내가 만든 todo list 랑 원리가 똑같다.
그리고 드래그 하려는 태그에 draggable='true'라는 속성이 있어야 드래그를 할 수 있으니 명심!

JS

input에 입력하면 drag이벤트가 입혀져야한다. 그리고 기존에 lists가 있으면 각각의 list에도 drag event가 입혀져야 한다.

// addEventsDragAndDrop을 이용해서 각각의 list에 모든 drag이벤트를 등록시킨다.
function addEventsDragAndDrop(el) {
  el.addEventListener('dragstart', dragStart);
  el.addEventListener('dragenter', dragEnter);
  el.addEventListener('dragover', dragOver);
  el.addEventListener('dragleave', dragLeave);
  el.addEventListener('drop', dragDrop);
  el.addEventListener('dragend', dragEnd);
}

var listItens = document.querySelectorAll('.draggable');
listItens.forEach(function (item) {
  addEventsDragAndDrop(item);
});

function addNewItem() {
  var newItem = document.querySelector('.input').value;
  if (newItem != '') {
    document.querySelector('.input').value = '';
    var li = document.createElement('li');
    var attr = document.createAttribute('draggable');
    var ul = document.querySelector('ul');
    li.className = 'draggable';
    attr.value = 'true';
    li.setAttributeNode(attr);
    li.appendChild(document.createTextNode(newItem));
    ul.appendChild(li);
    addEventsDragAndDrop(li);
  }
}

btn.addEventListener('click', addNewItem);

그리고 각각의 drag이벤트에 함수를 등록한다. 이벤트 진행상황에 따라 다른 함수가 실행이 된다. (상단에 짤을 보면 더 쉽게 이해가능하니 참고!)

  1. Start
    • start일때는 드래그 하려는 ele의 투명도가 줄어든다.
    • dragSrcEl 변수에 드래그하려는 내용을 따로 저장한다(나중에 drop할때 drop하는 대상의 속 내용과 교체하기 위해서 저장해둔다)
    • 그리고 dataTransfer.setData 메소드를 이용해서 속내용을 저장한다
    • 혹시 obj를 넘겨주고 싶으면 넘겨주기 전에 stringify를 해야한다.
 const dataObj = { content: this.innerHTML, id: this.id };
  e.dataTransfer.setData(DRAG_DATA_NAME, JSON.stringify(dataObj));
  1. Enter

    • Enter하려는 (정확히 말하면 Enter 당하는) 태그에 over라는 class를 추가한다. over 클래스는 scale을 확대하게 한다. 그래서 외부에서 Enter를 하면 커진다
  2. Leave

    • over 클래스를 제거해서 나갈때 다시 크기가 되돌아 온다.
  3. Over

    • 어떤 태그가 위에 머무를때 작동하는 이벤트이다. 따라서 Enter가 아닌 Over 이벤트에서 over 클래스를 추가해줘도 상관없다.
    • 다만 Over이벤트의 default를 MDN에서 살펴보면 Reset the current drag operation to "none". 라고 되어있다. drag 기능이 꺼진다는 뜻이다.
      따라서, default 기능을 비활성화 시켜줘야 작동한다. 그래서 preventDefault() 함수를 사용해 비활성화 시켜주자.
    • 그렇기 때문에 'enter할때 over 클래스를 추가하니깐 dragover 이벤트는 꺼둬도 되겠지?' 라고 생각하면 오산이다. dragover 이벤트에서 e.preventDefault를 해줘야 drop 이벤트가 실행이 된다. 따라서 dragover이벤트를 어찌되었든 등록시켜야 한다.
      요기 참고!
  4. Drop

    • drop 이벤트는 다른 drag이벤트와는 다르게 drag가 붙지 않는다. 그냥 drop이라고 적으면 된다. drop할때 드디어 dataTransfer.setData를 이용해 저장했던 기존 드래그 태그의 내용을 getData를 통해 꺼내온다음에 drop되는 태그와 교체한다.

    • start에서 stringify 했던 obj를 여기선 당연히 parsing 해서 사용해야한다.

const { content, id } = JSON.parse(e.dataTransfer.getData(DRAG_DATA_NAME));
  1. End
    • 그리고 기존의 드래그 되었던 태그의 opacity를 1로 되돌려 놓는다.

이 모든걸 코드로 나타내면 아래와 같다.


function dragStart(e) {
  this.style.opacity = '0.4';
  dragSrcEl = this;
  e.dataTransfer.setData('text/html', this.innerHTML);
  console.log('start');
  console.log(this);
}

function dragEnter(e) {
  // this.classList.add('over');
  console.log('enter');
  console.log(this);
}

function dragLeave(e) {
  this.classList.remove('over');
  console.log('leave');
  console.log(this);
}

function dragOver(e) {
  e.preventDefault();
  this.classList.add('over');
  console.log('over');
  console.log(this);
}

function dragDrop(e) {
  if (dragSrcEl != this) {
    console.log('drop');
    console.log(this);
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }
  this.classList.remove('over');
}

function dragEnd(e) {
  this.style.opacity = '1';
  console.log('end');
  console.log(this);
}

보면은 this를 로그해서 어떤 태그가 start,over,enter... 되는지 알아보았다.
start, over , leave는 드래그 되는 tag인데 enter 부터는 this가 교체되어지는 tag이다가 end는 또다시 드래그 되는 tag다.

즉, enter, drop은 당하는(?) 쪽에서 fire되는 이벤트인것이고 나머지는 드래그되는 tag가 활성화한 이벤트라고 보면 된다.

그러나 만약에 enter, drop이벤트가 list가 아닌 다른 곳, 예를 들어 div에 등록이 되어있다고 하면 div에 enter 하거나 drop했을때 this가 div일 것이다. 즉 , 이벤트가 어느 태그에 등록되어있느냐에 따라 drop되는 장소가 달라질 수 있다.(그리고 this도 달라진다)

결론

drag 이벤트가 드래그 되는 절차에 따라 세분화 되어있으며 data까지 넘기고 받을 수 있다는 것을 알았다. 좀 더 연습해서 내것으로 만들어보자!

profile
'과연 이게 최선일까?' 끊임없이 생각하기

0개의 댓글