W3docs

Arrastrar y Soltar con JavaScript

Aprende arrastrar y soltar en JavaScript: el enfoque con eventos de ratón (mousedown/mousemove/mouseup) y la API HTML5 nativa con draggable, dragover, drop y DataTransfer.

En este tutorial exploraremos la funcionalidad de arrastrar y soltar en JavaScript, una potente característica que mejora la interactividad en las páginas web. Hay dos formas de implementarla: el enfoque con eventos de ratón (mousedown/mousemove/mouseup), que te da control total sobre el movimiento, y la API HTML5 nativa de Drag and Drop (draggable + los eventos drag*), que está integrada en el navegador. Cubriremos ambas, veremos dónde encaja cada una y luego construiremos un ejemplo práctico donde un icono de bombilla ilumina un área oscura cuando lo arrastras hacia ella.

¿Qué es Arrastrar y Soltar?

Arrastrar y soltar es una interacción de interfaz de usuario que permite a los usuarios tomar un objeto y moverlo a una ubicación diferente en la pantalla. Lo ves en todas partes: arrastrando archivos en tu sistema operativo, reordenando elementos en un juego, subiendo fotos soltándolas sobre una página, o reordenando una lista de tareas. El patrón siempre tiene tres roles:

  • Un elemento arrastrable — el elemento que el usuario levanta.
  • Un destino de soltar (o droppable) — el área que puede recibirlo.
  • Un payload — datos opcionales llevados desde el origen al destino (por ejemplo, el id del elemento que se está moviendo).

Elegir un Enfoque

Ambas técnicas son válidas; elige según lo que necesites.

  • Eventos de ratón (mousedown, mousemove, mouseup) — tú rastreas manualmente el puntero y mueves el elemento por tu cuenta. Úsalos cuando necesitas que el elemento siga el cursor píxel a píxel (deslizadores, lienzos de forma libre, herramientas de dibujo, física personalizada). También es la base que se extiende a pantallas táctiles con touchstart/touchmove/touchend.
  • API HTML5 nativa de Drag and Drop (draggable="true" + dragstart/dragover/drop) — el navegador se encarga de la imagen "fantasma" y del gesto de arrastre, y te proporciona un objeto DataTransfer para transportar datos. Úsala para transferir algo entre zonas (subida de archivos, reordenamiento de listas, soltar elementos en una cesta). No funciona de forma nativa en dispositivos táctiles.

Demostraremos el enfoque con eventos de ratón en el ejemplo principal, y luego resumiremos la API nativa.

Conceptos Fundamentales de Arrastrar y Soltar en JavaScript

El Algoritmo de Arrastrar y Soltar

  1. Iniciar el Arrastre:
    • El proceso comienza cuando el usuario hace clic en el elemento y mantiene presionado el botón del ratón.
  2. Arrastrar el Elemento:
    • A medida que el ratón se mueve, el elemento sigue el camino del cursor por la pantalla.
  3. Soltar el Elemento:
    • El elemento se libera cuando el usuario suelta el botón del ratón, colocando el elemento en una nueva posición.

Entender los Droppables

Los droppables son áreas designadas para recibir los elementos arrastrables. Estas áreas detectan cuándo un objeto arrastrable está sobre ellas y pueden desencadenar acciones específicas como respuesta.

Información

Asegúrate de que tu funcionalidad de arrastrar y soltar sea compatible con pantallas táctiles. Los usuarios móviles deben poder arrastrar y soltar mediante gestos táctiles. Considera implementar eventos táctiles (touchstart, touchmove, touchend) o usar una biblioteca ligera para compatibilidad entre dispositivos.

Ejemplo Interactivo: Área Clara y Oscura

Pongamos la teoría en práctica con un ejemplo simple pero interactivo. Usaremos un icono de bombilla como objeto arrastrable. Cuando este icono se mueva sobre un área oscura, el área se iluminará, simulando el efecto de encender una luz.

Configurar el HTML y el CSS

Primero, definimos la estructura básica y el estilo. Incluimos una caja oscura y un icono de bombilla.

Estructura HTML

<!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>

Implementar el JavaScript

Ahora, añadamos la funcionalidad para hacer que la bombilla sea arrastrable y reactiva al área oscura.

Explicación del Código JavaScript

<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>

Este script hace que la bombilla sea arrastrable con el ratón y reacciona cuando la sueltas sobre el área oscura. Esto es lo que hace cada parte:

  1. Configuración: El script obtiene referencias al icono de la bombilla y al área oscura, y declara las variables que usa para rastrear el arrastre (active, la posición inicial del ratón, el desplazamiento actual).
  2. Iniciar el arrastre (mousedown): Cuando presionas el botón del ratón sobre la bombilla, el script registra la posición inicial del cursor (menos cualquier desplazamiento de un arrastre anterior) y establece active = true. El desplazamiento importa — sin él, el icono volvería al origen cada vez que inicias un nuevo arrastre.
  3. Mover (mousemove): Mientras active es true, el script calcula cuánto se ha desplazado el cursor y lo aplica como una transformación translate3d, de modo que el icono sigue el puntero. Si active es false, el manejador retorna inmediatamente y no hace nada.
  4. Soltar (mouseup): Cuando sueltas el botón, el script guarda la posición final, establece active = false y llama a isInside para decidir si la bombilla cayó dentro del área oscura. Si es así, tanto el área como la bombilla se vuelven amarillas; de lo contrario, vuelven a sus colores predeterminados.
  5. Prueba de colisión (isInside): Este auxiliar compara los rectángulos delimitadores de los dos elementos con getBoundingClientRect() y devuelve true solo cuando la bombilla está completamente contenida dentro de los bordes del área oscura.
Información

Usa translate3d en tus transformaciones CSS para arrastrar elementos. Aprovecha la aceleración por GPU, lo que conduce a movimientos más suaves y menor carga en la CPU, algo crucial para aplicaciones con uso intensivo del rendimiento.

Ejemplo Completo

Ahora es momento de verlo todo en acción:

<!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>

Eventos de Ratón Clave Utilizados:

  1. mousedown: Este evento se activa cuando el usuario presiona el botón del ratón sobre el icono de la bombilla. Marca el inicio del arrastre y registra la posición inicial del cursor.
  2. mousemove: Este evento se dispara cuando se mueve el ratón. Si el arrastre está activo (es decir, el botón del ratón sigue presionado), calcula la nueva posición del icono en función del movimiento del cursor y actualiza la posición de la bombilla en la pantalla.
  3. mouseup: Este evento ocurre cuando el usuario suelta el botón del ratón, marcando el final del arrastre. Comprueba si la bombilla está dentro de los límites del área oscura para decidir si cambiar el color de fondo del área.

Como aprendimos en el artículo Eventos de Ratón, estos eventos son fundamentales para crear la funcionalidad de arrastrar y soltar de forma interactiva, permitiendo que los elementos de una página web se muevan dinámicamente e interactúen con el ratón. (Si quieres posicionar el elemento con precisión, el capítulo Coordenadas en JavaScript explica la diferencia entre clientX/clientY, pageX/pageY y getBoundingClientRect().)

La API HTML5 Nativa de Drag and Drop

La técnica con eventos de ratón es excelente cuando necesitas que un elemento siga el cursor exactamente. Pero cuando tu objetivo real es transferir algo — soltar una tarjeta en una columna, un elemento en un carrito, un archivo en una zona de carga — la API de Drag and Drop integrada en el navegador requiere menos código y gestiona el gesto de arrastre por ti.

Hacer un Elemento Arrastrable

Cualquier elemento se vuelve arrastrable al establecer el atributo draggable en true. Los enlaces y las imágenes son arrastrables por defecto; todo lo demás no lo es.

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

Los Eventos de Drag and Drop

La API dispara una secuencia de eventos. Los más importantes son:

EventoSe dispara enCuándo
dragstartel elemento arrastradoen el momento en que comienza el arrastre
dragoverel destino de soltarcontinuamente mientras un arrastrable está sobre él
dropel destino de soltarcuando el usuario lo suelta encima
dragendel elemento arrastradocuando el arrastre termina (soltado o cancelado)

Transportar Datos con DataTransfer

Cada evento de arrastre expone event.dataTransfer, un objeto usado para adjuntar un payload en dragstart y leerlo de vuelta en 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));
});
Advertencia

El error más común con la API nativa es olvidar e.preventDefault() dentro del manejador dragover. Sin él, el navegador rechaza el elemento como destino de soltar y el evento drop nunca se dispara. Los datos que estableces con setData solo están disponibles de nuevo cuando se ejecuta drop — no se pueden leer durante dragover por razones de seguridad.

El par setData(format, value) / getData(format) te permite transportar texto plano, URLs (text/uri-list), HTML o tus propias claves string personalizadas. Para subidas de archivos, lee e.dataTransfer.files, que es un FileList igual que un <input type="file">.

Eventos de Ratón vs. API Nativa de un Vistazo

  • Recurre a los eventos de ratón cuando el movimiento debe ser libre y con precisión de píxel, o cuando necesitas soporte táctil.
  • Recurre a la API nativa cuando estás moviendo un elemento discreto de un lugar a otro y quieres que el navegador gestione la imagen y el gesto de arrastre.

Para profundizar en el sistema de eventos subyacente, consulta Introducción a los Eventos del Navegador, y para las interacciones accesibles recuerda que arrastrar y soltar siempre debe tener una alternativa con teclado — consulta Consideraciones de Accesibilidad del DOM.

Conclusión

Arrastrar y soltar hace que las interfaces se sientan directas e intuitivas. Ahora tienes dos herramientas para ello: el enfoque con eventos de ratón, que mueve un elemento píxel a píxel bajo control manual total, y la API HTML5 nativa de Drag and Drop, que permite al navegador gestionar el gesto mientras transportas datos a través de DataTransfer. Elige los eventos de ratón para movimiento libre y soporte táctil, y la API nativa para transferir elementos entre zonas. Cualquiera que elijas, proporciona una alternativa accesible con teclado para que todos los usuarios puedan completar la misma tarea.

Práctica

Práctica
¿Cuáles son afirmaciones verdaderas sobre la funcionalidad de arrastrar y soltar en JavaScript?
¿Cuáles son afirmaciones verdaderas sobre la funcionalidad de arrastrar y soltar en JavaScript?
Was this page helpful?