W3docs

JavaScript Drag and Drop

Learn drag-and-drop in JavaScript: the mouse-event approach (mousedown/mousemove/mouseup) and the HTML5 Drag and Drop API with draggable and drop events.

In this tutorial, we'll explore the drag-and-drop functionality in JavaScript, a powerful feature that enhances interactivity on web pages. There are two ways to build it: the mouse-event approach (mousedown/mousemove/mouseup), which gives you total control over movement, and the native HTML5 Drag and Drop API (draggable + the drag* events), which is built into the browser. We'll cover both, see where each one fits, then build a hands-on example where a light bulb icon lights up a dark area when you drag it in.

What is Drag-and-Drop?

Drag-and-drop is a user interface interaction that lets users grab an object and move it to a different location on the screen. You see it everywhere: dragging files in your operating system, rearranging items in a game, uploading photos by dropping them onto a page, or reordering a to-do list. The pattern always has three roles:

  • A draggable — the element the user picks up.
  • A drop target (or droppable) — the area that can receive it.
  • A payload — optional data carried from the source to the target (for example, the id of the item being moved).

Choosing an Approach

Both techniques are valid; pick based on what you need.

  • Mouse events (mousedown, mousemove, mouseup) — you manually track the pointer and move the element yourself. Use this when you need the element to follow the cursor pixel-by-pixel (sliders, free-form canvases, drawing tools, custom physics). It's also what you extend to touch screens with touchstart/touchmove/touchend.
  • Native HTML5 Drag and Drop (draggable="true" + dragstart/dragover/drop) — the browser handles the "ghost" image and the dragging gesture for you, and gives you a DataTransfer object to carry data. Use this for transferring something between zones (file uploads, reordering lists, dropping items into a basket). It does not work out of the box on touch devices.

We'll demonstrate the mouse-event approach in the main example, then summarize the native API.

Core Concepts of Drag-and-Drop in JavaScript

The Drag'n'Drop Algorithm

  1. Start the Drag:
    • The process begins when the user clicks on the element and holds down the mouse button.
  2. Dragging the Element:
    • As the mouse moves, the element follows the cursor's path across the screen.
  3. Drop the Element:
    • The element is released when the user lets go of the mouse button, placing the element in a new position.

Understanding Droppables

Droppables are areas designated to receive the draggable elements. These areas detect when a draggable object is over them and can trigger specific actions as a response.

Info

Ensure your drag-and-drop functionality is touch-friendly. Mobile users should be able to drag and drop with touch gestures. Consider implementing touch events (touchstart, touchmove, touchend) or using a lightweight library for cross-device compatibility.

Interactive Example: Light and Dark Area

Let’s put theory into practice with a simple but interactive example. We will use a light bulb icon as our draggable object. When this icon is moved over a dark area, the area will light up, simulating the effect of a light being turned on.

Setting Up the HTML and CSS

First, we define the basic structure and style. We include a dark box and a light bulb icon.

HTML Structure

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Lighting with Drag and Drop</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
<style>
  #darkArea {
    width: 300px;
    height: 300px;
    background-color: #333;
    position: relative;
    margin-top: 20px;
  }
  #lightIcon {
    font-size: 48px;
    color: #ccc;
    cursor: pointer;
    position: absolute;
  }
</style>
</head>
<body>
<div id="main">
  <div id="darkArea"></div>
  <i id="lightIcon" class="fas fa-lightbulb"></i>
</div>

<script>
// JavaScript will be added here.
</script>
</body>
</html>

Implementing the JavaScript

Now, let's add functionality to make the light bulb draggable and reactive to the dark area.

JavaScript Code Explanation

<script>
  // Get references to the light bulb icon and the dark area on the webpage
  var lightIcon = document.getElementById("lightIcon");
  var darkArea = document.getElementById("darkArea");

  // Variables to track whether the dragging is active and to store position data
  var active = false;
  var initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;

  // Listen for the mouse down event on the light bulb icon
  lightIcon.addEventListener("mousedown", function(e) {
    // Record the starting position of the mouse and adjust by any existing offset
    initialX = e.clientX - xOffset;
    initialY = e.clientY - yOffset;
    // Set the active flag to true, indicating that dragging has started
    active = true;
  });

  // Listen for mouse movement across the entire document
  document.addEventListener("mousemove", function(e) {
    // If not dragging, don't do anything
    if (!active) return;
    // Calculate the new position of the mouse
    currentX = e.clientX - initialX;
    currentY = e.clientY - initialY;
    // Update the offset with the new position
    xOffset = currentX;
    yOffset = currentY;
    // Move the light bulb icon to the new position
    lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
  });

  // Listen for the mouse up event across the entire document
  document.addEventListener("mouseup", function() {
    // Save the final position of the light bulb
    initialX = currentX;
    initialY = currentY;
    // Set the active flag to false, indicating dragging has ended
    active = false;
    // Check if the light bulb is inside the dark area
    if (isInside(darkArea, lightIcon)) {
      // Change the background color of the dark area to yellow
      darkArea.style.backgroundColor = "yellow";
      // Change the color of the light bulb to yellow
      lightIcon.style.color = "yellow";
    } else {
      // Revert the dark area's color to dark
      darkArea.style.backgroundColor = "#333";
      // Revert the light bulb's color to gray
      lightIcon.style.color = "#ccc";
    }
  });

  // Function to check if the light bulb is inside the dark area
  function isInside(container, element) {
    // Get the position of the container and the element
    var containerRect = container.getBoundingClientRect();
    var elementRect = element.getBoundingClientRect();
    // Return true if the element is within the container's boundaries
    return (
      elementRect.left >= containerRect.left &&
      elementRect.right <= containerRect.right &&
      elementRect.top >= containerRect.top &&
      elementRect.bottom <= containerRect.bottom
    );
  }
</script>

This script makes the light bulb draggable with the mouse and reacts when you drop it over the dark area. Here is what each part does:

  1. Setup: The script grabs references to the light bulb icon and the dark area, and declares the variables it uses to track the drag (active, the initial mouse position, the current offset).
  2. Start to drag (mousedown): When you press the mouse button on the light bulb, the script records the starting cursor position (minus any offset from a previous drag) and sets active = true. The offset matters — without it, the icon would jump back to the origin each time you start a new drag.
  3. Move (mousemove): While active is true, the script computes how far the cursor has travelled and applies that as a translate3d transform, so the icon follows the pointer. If active is false, the handler returns immediately and does nothing.
  4. Drop (mouseup): When you release the button, the script stores the final position, sets active = false, and calls isInside to decide whether the bulb landed in the dark area. If it did, both the area and the bulb turn yellow; otherwise they reset to their default colors.
  5. Hit testing (isInside): This helper compares the bounding rectangles of the two elements with getBoundingClientRect() and returns true only when the bulb is fully contained inside the dark area's edges.
Info

Use translate3d in your CSS transformations for dragging elements. It utilizes GPU acceleration, leading to smoother movements and less CPU load, which is crucial for performance-intensive applications.

Full Example

Now, it is time to see it all in action:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Interactive Lighting with Drag and Drop</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" />
    <style>
      #darkArea {
        width: 300px;
        height: 300px;
        background-color: #333;
        position: relative;
        margin-top: 20px;
      }
      #lightIcon {
        font-size: 48px;
        color: #ccc;
        cursor: pointer;
        position: absolute;
      }
    </style>
  </head>
  <body>
    <div id="main">
     <p>Move the light into the dark area to light it up!</p>
      <div id="darkArea"></div>
      <i id="lightIcon" class="fas fa-lightbulb"></i>
    </div>

    <script>
      // Get references to the light bulb icon and the dark area on the webpage
      var lightIcon = document.getElementById("lightIcon");
      var darkArea = document.getElementById("darkArea");

      // Variables to track whether the dragging is active and to store position data
      var active = false;
      var initialX,
        initialY,
        currentX,
        currentY,
        xOffset = 0,
        yOffset = 0;

      // Listen for the mouse down event on the light bulb icon
      lightIcon.addEventListener("mousedown", function (e) {
        // Record the starting position of the mouse and adjust by any existing offset
        initialX = e.clientX - xOffset;
        initialY = e.clientY - yOffset;
        // Set the active flag to true, indicating that dragging has started
        active = true;
      });

      // Listen for mouse movement across the entire document
      document.addEventListener("mousemove", function (e) {
        // If not dragging, don't do anything
        if (!active) return;
        // Calculate the new position of the mouse
        currentX = e.clientX - initialX;
        currentY = e.clientY - initialY;
        // Update the offset with the new position
        xOffset = currentX;
        yOffset = currentY;
        // Move the light bulb icon to the new position
        lightIcon.style.transform = "translate3d(" + currentX + "px, " + currentY + "px, 0)";
      });

      // Listen for the mouse up event across the entire document
      document.addEventListener("mouseup", function () {
        // Save the final position of the light bulb
        initialX = currentX;
        initialY = currentY;
        // Set the active flag to false, indicating dragging has ended
        active = false;
        // Check if the light bulb is inside the dark area
        if (isInside(darkArea, lightIcon)) {
          // Change the background color of the dark area to yellow
          darkArea.style.backgroundColor = "yellow";
          lightIcon.style.color = "yellow";
        } else {
          // Revert the dark area's color to dark
          darkArea.style.backgroundColor = "#333";
          // Revert the light bulb's color to gray
          lightIcon.style.color = "#ccc";
        }
      });

      // Function to check if the light bulb is inside the dark area
      function isInside(container, element) {
        // Get the position of the container and the element
        var containerRect = container.getBoundingClientRect();
        var elementRect = element.getBoundingClientRect();
        // Return true if the element is within the container's boundaries
        return elementRect.left >= containerRect.left && elementRect.right <= containerRect.right && elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
      }
    </script>
  </body>
</html>

Key Mouse Events Used:

  1. mousedown: This event is triggered when the user presses the mouse button over the light bulb icon. It marks the start of the drag and records the initial position of the cursor.
  2. mousemove: This event fires when the mouse is moved. If the drag is active (i.e., the mouse button is still pressed), it calculates the new position of the icon based on the cursor's movement and updates the position of the light bulb on the screen.
  3. mouseup: This event occurs when the user releases the mouse button, marking the end of the drag. It checks whether the light bulb is within the boundaries of the dark area to decide whether to change the area's background color.

As we learned in the Mouse Events article, these events are fundamental for creating interactive drag-and-drop functionality, allowing elements on a webpage to be dynamically moved and interacted with using a mouse. (If you want to position the element precisely, the JavaScript Coordinates chapter explains the difference between clientX/clientY, pageX/pageY, and getBoundingClientRect().)

The Native HTML5 Drag and Drop API

The mouse-event technique is great when you need an element to follow the cursor exactly. But when your real goal is to transfer something — drop a card into a column, an item into a cart, a file onto an upload zone — the browser's built-in Drag and Drop API is less code and handles the drag gesture for you.

Making an Element Draggable

Any element becomes draggable by setting the draggable attribute to true. Links and images are draggable by default; everything else is not.

<div id="item" draggable="true">Drag me</div>
<div id="dropzone">Drop here</div>

The Drag and Drop Events

The API fires a sequence of events. The most important ones are:

EventFires onWhen
dragstartthe dragged elementthe moment the drag begins
dragoverthe drop targetcontinuously while a draggable is over it
dropthe drop targetwhen the user releases over it
dragendthe dragged elementwhen the drag finishes (dropped or cancelled)

Carrying Data with DataTransfer

Each drag event exposes event.dataTransfer, an object used to attach a payload in dragstart and read it back in drop.

const item = document.getElementById("item");
const zone = document.getElementById("dropzone");

// 1. Attach a payload when the drag starts.
item.addEventListener("dragstart", (e) => {
  e.dataTransfer.setData("text/plain", item.id);
});

// 2. By default elements are NOT valid drop targets.
//    Prevent the default to allow a drop.
zone.addEventListener("dragover", (e) => {
  e.preventDefault();
});

// 3. Read the payload and move the element on drop.
zone.addEventListener("drop", (e) => {
  e.preventDefault();
  const id = e.dataTransfer.getData("text/plain");
  zone.appendChild(document.getElementById(id));
});
Warning

The single most common mistake with the native API is forgetting e.preventDefault() inside the dragover handler. Without it, the browser rejects the element as a drop target and the drop event never fires. The data you set with setData is only available again once drop runs — it cannot be read during dragover for security reasons.

The setData(format, value) / getData(format) pair lets you carry plain text, URLs (text/uri-list), HTML, or your own custom string keys. For file uploads, read e.dataTransfer.files, which is a FileList just like an <input type="file">.

Mouse Events vs. Native API at a Glance

  • Reach for mouse events when movement must be free-form and pixel-accurate, or when you need touch support.
  • Reach for the native API when you're moving a discrete item from one place to another and want the browser to manage the drag image and gesture.

To go deeper on the underlying event system, see Introduction to Browser Events, and for accessible interactions remember that drag-and-drop should always have a keyboard alternative — see DOM Accessibility Considerations.

Conclusion

Drag-and-drop makes interfaces feel direct and intuitive. You now have two tools for it: the mouse-event approach, which moves an element pixel-by-pixel under full manual control, and the native HTML5 Drag and Drop API, which lets the browser manage the gesture while you carry data through DataTransfer. Choose mouse events for free-form movement and touch support, and the native API for transferring items between zones. Whichever you pick, provide a keyboard-accessible alternative so every user can complete the same task.

Practice

Practice
What are true statements about the drag and drop functionality in JavaScript?
What are true statements about the drag and drop functionality in JavaScript?
Was this page helpful?