HTML5 드래그 앤 드롭 API

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

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

대부분의 브라우저에서 기본적으로 텍스트 선택, 이미지 및 링크를 드래그할 수 있습니다. 예를 들어 웹페이지의 링크를 드래그하면 제목과 URL이 포함된 작은 상자가 표시됩니다. 이 상자를 주소 표시줄이나 데스크톱에 드롭하여 바로가기를 만들거나 링크로 이동할 수 있습니다. 다른 유형의 콘텐츠를 드래그 가능하도록 만들려면 HTML5 드래그 앤 드롭 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;
}

다음 데모에서 결과를 확인할 수 있습니다. 이 기능이 작동하려면 데스크톱 브라우저가 필요합니다. 드래그 앤 드롭 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.
  }
}

자세한 내용은 맞춤 드래그 앤 드롭을 참고하세요.

추가 리소스