JavaScript Drag and Drop API: File Drops and Sorting

The HTML5 Drag and Drop (DnD) API provides a native mechanism for enabling complex drag interactions within web applications. This article covers how the API processes external file drops from a user’s operating system and how it facilitates internal UI element sorting. You will learn the core lifecycle events, the critical role of the DataTransfer object, and the practical implementation patterns for both file handling and dynamic list reordering.

The Core Drag and Drop Lifecycle

The Drag and Drop API relies on a series of events fired on both the dragged item (the source) and the drop zone (the target).

Communication between the source and target occurs via the event.dataTransfer object, which holds the payload (data, files, or visual drag indicators).


Handling File Drops

Handling external file drops allows users to drag files from their desktop or file explorer directly into the browser.

1. Preparing the Drop Target

By default, browsers navigate to or open dropped files. To allow a custom drop interaction, you must prevent this default behavior inside both the dragover and dragenter event handlers.

const dropZone = document.getElementById('drop-zone');

dropZone.addEventListener('dragover', (event) => {
  event.preventDefault(); // Required to allow a drop
  event.dataTransfer.dropEffect = 'copy'; // Visual feedback
});

2. Extracting Files on drop

When the file is released, the drop event fires. Files are retrieved through event.dataTransfer.files (a FileList) or event.dataTransfer.items (a DataTransferItemList for advanced handling such as directory inspection).

dropZone.addEventListener('drop', (event) => {
  event.preventDefault();

  const files = event.dataTransfer.files;
  if (files.length > 0) {
    handleFiles(files);
  }
});

function handleFiles(files) {
  Array.from(files).forEach((file) => {
    console.log(`File Name: ${file.name}, Size: ${file.size} bytes`);
    // Process with FileReader or attach to a FormData object for upload
  });
}

Implementing UI Sorting (List Reordering)

UI sorting involves moving DOM elements internally within the page. This requires making elements draggable, tracking the dragged node, and dynamically altering the DOM structure.

1. Enabling Draggable Elements

Add the draggable="true" attribute to any element that can be moved.

<ul id="sortable-list">
  <li draggable="true" class="item">Item 1</li>
  <li draggable="true" class="item">Item 2</li>
  <li draggable="true" class="item">Item 3</li>
</ul>

2. Managing the Drag Source

Use dragstart to track which element is actively being moved, and dragend to reset any visual states.

let draggedItem = null;

const list = document.getElementById('sortable-list');

list.addEventListener('dragstart', (event) => {
  draggedItem = event.target;
  event.dataTransfer.effectAllowed = 'move';
  // Optional: Set text/plain payload if needed
  event.dataTransfer.setData('text/plain', '');
  event.target.classList.add('dragging');
});

list.addEventListener('dragend', (event) => {
  event.target.classList.remove('dragging');
  draggedItem = null;
});

3. Calculating Drop Position and Sorting

Listen for the dragover event on the container. Determine the position of the cursor relative to neighboring elements to insert the dragged node before or after the target node.

list.addEventListener('dragover', (event) => {
  event.preventDefault();
  event.dataTransfer.dropEffect = 'move';

  const target = event.target.closest('.item');
  
  if (target && target !== draggedItem) {
    const bounding = target.getBoundingClientRect();
    const offset = event.clientY - bounding.top;

    // If mouse is more than halfway down the target element, insert after it
    if (offset > bounding.height / 2) {
      target.after(draggedItem);
    } else {
      target.before(draggedItem);
    }
  }
});

Summary of Differences

Feature External File Drops UI Element Sorting
Origin Operating system / local disk In-browser DOM nodes
Draggable Attribute Not required draggable="true" required
Data Extraction event.dataTransfer.files DOM node reference / dataTransfer.setData()
Target Requirement Call event.preventDefault() on dragover Call event.preventDefault() and mutate DOM on dragover / drop