HTML5 드래그 앤 드롭 API

이 게시물에서는 드래그 앤 드롭의 기본사항을 설명합니다.

드래그 가능한 콘텐츠 만들기

대부분의 브라우저에서는 텍스트 선택사항, 이미지, 링크를 기본적으로 드래그할 수 있습니다. 예를 들어 웹페이지에서 링크를 드래그하면 제목과 URL이 포함된 작은 상자가 표시됩니다. 이 상자를 주소 표시줄이나 데스크톱에 드롭하여 바로가기를 만들거나 링크로 이동할 수 있습니다. 다른 유형의 콘텐츠를 드래그할 수 있게 하려면 HTML5 Drag and Drop API를 사용해야 합니다.

객체를 드래그 가능하도록 설정하려면 해당 요소에 draggable=true를 설정합니다. 이미지, 파일, 링크, 파일 또는 페이지의 마크업을 비롯하여 거의 모든 항목을 드래그할 수 있습니다.

다음 예에서는 CSS 그리드로 배치된 열을 재정렬하는 인터페이스를 만듭니다. 열의 기본 마크업은 다음과 같으며 각 열의 draggable 속성은 true로 설정됩니다.

<div class="container">
  <div draggable="true" class="box">A</div>
  <div draggable="true" class="box">B</div>
  <div draggable="true" class="box">C</div>
</div>

다음은 컨테이너 및 상자 요소의 CSS입니다. 드래그 기능과 관련된 유일한 CSS는 cursor: move 속성입니다. 나머지 코드는 컨테이너 및 상자 요소의 레이아웃과 스타일을 제어합니다.

.container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
}

.box {
  border: 3px solid #666;
  background-color: #ddd;
  border-radius: .5em;
  padding: 10px;
  cursor: move;
}

이 시점에서 항목을 드래그할 수 있지만 다른 작업은 할 수 없습니다. 동작을 추가하려면 JavaScript API를 사용해야 합니다.

드래그 이벤트 수신 대기

드래그 프로세스를 모니터링하려면 다음 이벤트 중 하나를 수신 대기하면 됩니다.

드래그 흐름을 처리하려면 일종의 소스 요소 (드래그가 시작되는 위치), 데이터 페이로드 (드래그되는 항목), 타겟 (드롭을 포착하는 영역)이 필요합니다. 소스 요소는 거의 모든 종류의 요소가 될 수 있습니다. 타겟은 사용자가 드롭하려는 데이터를 수락하는 드롭 영역 또는 드롭 영역 집합입니다. 일부 요소는 타겟이 될 수 없습니다. 예를 들어 타겟은 이미지가 될 수 없습니다.

드래그 시퀀스 시작 및 종료

콘텐츠에서 draggable="true" 속성을 정의한 후 dragstart 이벤트 핸들러를 연결하여 각 열의 드래그 시퀀스를 시작합니다.

이 코드는 사용자가 열을 드래그하기 시작하면 열의 불투명도를 40% 로 설정한 다음 드래그 이벤트가 종료되면 100% 로 되돌립니다.

function handleDragStart(e) {
  this.style.opacity = '0.4';
}

function handleDragEnd(e) {
  this.style.opacity = '1';
}

let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
  item.addEventListener('dragstart', handleDragStart);
  item.addEventListener('dragend', handleDragEnd);
});

결과는 다음 Glitch 데모에서 확인할 수 있습니다. 항목을 드래그하면 불투명도가 변경됩니다. 소스 요소에 dragstart 이벤트가 있으므로 this.style.opacity를 40% 로 설정하면 사용자에게 해당 요소가 현재 선택된 요소이며 이동 중임을 시각적으로 알려줍니다. 항목을 배치하면 아직 배치 동작을 정의하지 않았더라도 소스 요소가 100% 불투명도로 돌아갑니다.

시각적 신호 추가

사용자가 인터페이스와 상호작용하는 방법을 이해할 수 있도록 dragenter, dragover, dragleave 이벤트 핸들러를 사용하세요. 이 예에서 열은 드래그할 수 있을 뿐만 아니라 드롭 타겟이기도 합니다. 사용자가 열 위로 드래그한 항목을 길게 누르면 테두리가 점선으로 표시되도록 하여 사용자가 이를 이해하도록 지원합니다. 예를 들어 CSS에서 드롭 타겟인 요소의 over 클래스를 만들 수 있습니다.

.box.over {
  border: 3px dotted #666;
}

그런 다음 JavaScript에서 이벤트 핸들러를 설정하고 열을 드래그할 때 over 클래스를 추가하고 드래그된 요소가 나갈 때 삭제합니다. dragend 핸들러에서는 드래그가 끝날 때 클래스를 삭제합니다.

document.addEventListener('DOMContentLoaded', (event) => {

  function handleDragStart(e) {
    this.style.opacity = '0.4';
  }

  function handleDragEnd(e) {
    this.style.opacity = '1';

    items.forEach(function (item) {
      item.classList.remove('over');
    });
  }

  function handleDragOver(e) {
    e.preventDefault();
    return false;
  }

  function handleDragEnter(e) {
    this.classList.add('over');
  }

  function handleDragLeave(e) {
    this.classList.remove('over');
  }

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });
});

이 코드에서 다루어야 할 몇 가지 사항이 있습니다.

  • dragover 이벤트의 기본 작업dataTransfer.dropEffect 속성을 "none"로 설정하는 것입니다. dropEffect 속성은 이 페이지의 뒷부분에서 다룹니다. 지금은 drop 이벤트가 실행되지 않도록 방지한다는 사실만 알아두세요. 이 동작을 재정의하려면 e.preventDefault()를 호출하세요. 동일한 핸들러에서 false를 반환하는 것도 좋습니다.

  • dragenter 이벤트 핸들러는 dragover 대신 over 클래스를 전환하는 데 사용됩니다. dragover를 사용하면 사용자가 열 위로 드래그한 항목을 누르고 있는 동안 이벤트가 반복적으로 발생하여 CSS 클래스가 반복적으로 전환됩니다. 이렇게 하면 브라우저에서 불필요한 렌더링 작업을 많이 실행하게 되어 사용자 환경에 영향을 미칠 수 있습니다. 다시 그리기를 최소화하는 것이 좋으며 dragover를 사용해야 하는 경우 이벤트 리스너를 제한하거나 디버빙하는 것이 좋습니다.

드롭 완료

드롭을 처리하려면 drop 이벤트의 이벤트 리스너를 추가합니다. drop 핸들러에서 일반적으로 성가신 리디렉션과 같은 브라우저의 기본 드롭 동작을 방지해야 합니다. 이렇게 하려면 e.stopPropagation()를 호출합니다.

function handleDrop(e) {
  e.stopPropagation(); // stops the browser from redirecting.
  return false;
}

새 핸들러를 다른 핸들러와 함께 등록해야 합니다.

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });

이 시점에서 코드를 실행하면 항목이 새 위치로 이동하지 않습니다. 이렇게 하려면 DataTransfer 객체를 사용하세요.

dataTransfer 속성에는 드래그 작업에서 전송된 데이터가 포함됩니다. dataTransferdragstart 이벤트에서 설정되고 드롭 이벤트에서 읽거나 처리됩니다. e.dataTransfer.setData(mimeType, dataPayload)를 호출하면 객체의 MIME 유형과 데이터 페이로드를 설정할 수 있습니다.

이 예에서는 사용자가 열의 순서를 재정렬할 수 있도록 합니다. 이렇게 하려면 먼저 드래그가 시작될 때 소스 요소의 HTML을 저장해야 합니다.

function handleDragStart(e) {
  this.style.opacity = '0.4';

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

drop 이벤트에서 소스 열의 HTML을 데이터를 배치한 대상 열의 HTML로 설정하여 열 드롭을 처리합니다. 여기에는 사용자가 드래그한 것과 동일한 열에 다시 놓지 않는지 확인하는 것이 포함됩니다.

function handleDrop(e) {
  e.stopPropagation();

  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

다음 데모에서 결과를 확인할 수 있습니다. 이렇게 하려면 데스크톱 브라우저가 필요합니다. Drag and Drop API는 모바일에서 지원되지 않습니다. A 열을 B 열 위로 드래그한 다음 놓으면 열의 위치가 어떻게 바뀌는지 확인할 수 있습니다.

더 많은 드래그 속성

dataTransfer 객체는 속성을 노출하여 드래그 프로세스 중에 사용자에게 시각적 피드백을 제공하고 각 드롭 타겟이 특정 데이터 유형에 응답하는 방식을 제어합니다.

  • dataTransfer.effectAllowed는 사용자가 요소에서 실행할 수 있는 '드래그 유형'을 제한합니다. 드래그 앤 드롭 처리 모델에서 dragenterdragover 이벤트 중에 dropEffect를 초기화하는 데 사용됩니다. 이 속성은 none, copy, copyLink, copyMove, link, linkMove, move, all, uninitialized 값을 가질 수 있습니다.
  • dataTransfer.dropEffectdragenterdragover 이벤트 중에 사용자가 받는 의견을 제어합니다. 사용자가 타겟 요소 위로 포인터를 가져가면 브라우저의 커서가 복사나 이동과 같은 어떤 유형의 작업이 실행될지 나타냅니다. 효과는 none, copy, link, move 값 중 하나를 사용할 수 있습니다.
  • e.dataTransfer.setDragImage(imgElement, x, y)는 브라우저의 기본 '유령 이미지' 의견을 사용하는 대신 드래그 아이콘을 설정할 수 있음을 의미합니다.

파일 업로드

이 간단한 예에서는 열을 드래그 소스와 드래그 타겟으로 모두 사용합니다. 이는 사용자에게 항목을 재정렬하라는 메시지를 표시하는 UI에서 발생할 수 있습니다. 경우에 따라 드래그 타겟과 소스가 다른 요소 유형일 수 있습니다. 예를 들어 사용자가 선택한 이미지를 타겟 위로 드래그하여 제품의 기본 이미지로 하나의 이미지를 선택해야 하는 인터페이스가 여기에 해당합니다.

드래그 앤 드롭은 사용자가 데스크톱에서 애플리케이션으로 항목을 드래그하는 데 자주 사용됩니다. 주요 차이점은 drop 핸들러에 있습니다. dataTransfer.getData()를 사용하여 파일에 액세스하는 대신 데이터가 dataTransfer.files 속성에 포함됩니다.

function handleDrop(e) {
  e.stopPropagation(); // Stops some browsers from redirecting.
  e.preventDefault();

  var files = e.dataTransfer.files;
  for (var i = 0, f; (f = files[i]); i++) {
    // Read the File objects in this FileList.
  }
}

자세한 내용은 커스텀 드래그 앤 드롭을 참고하세요.

추가 리소스