Drag and Drop en HTML5 y JavaScript: Guía Completa con Ejemplos Reales

- Andrés Cruz - EN In english

Drag and Drop en HTML5 y JavaScript: Guía Completa con Ejemplos Reales

El sistema de Drag and Drop (arrastrar y soltar) es una de esas funcionalidades que, bien implementadas, elevan la experiencia de usuario sin necesidad de librerías externas. Llevo años usándolo en proyectos reales —desde reorganizar listas de tareas hasta cargar archivos en interfaces conectadas a backends con Laravel o Django— y todavía me sorprende lo flexible que puede ser con solo HTML5 y JavaScript nativo.

¿Qué es Drag and Drop en HTML5? Es una interfaz de usuario que permite mover elementos de un lugar a otro usando el ratón o el dedo. En web, HTML5 lo hace nativo mediante la API DataTransfer, sin necesidad de librerías de terceros.

En esta guía te explico todo: desde los eventos fundamentales hasta ejemplos avanzados y listos para producción, para que puedas implementar DnD en minutos y dominarlo en horas.

Antes vimos cómo generar archivos en JavaScript, así como leerlos y manipularlos.

Qué es el Drag and Drop y por qué sigue siendo tan relevante

El Drag and Drop permite agarrar un elemento, moverlo y soltarlo en otro lugar de la interfaz. Aunque suena simple, es una de las interacciones más intuitivas para los usuarios, porque replica exactamente lo que hacemos en el mundo físico: coger un objeto y dejarlo en otro sitio.

Cómo funciona la interacción de arrastrar y soltar

El sistema se apoya en tres piezas clave que siempre deben estar presentes:

  • Elemento arrastrable (draggable): el elemento del DOM que el usuario puede coger y mover.
  • Datos a transferir (dataTransfer): el payload de información que "viaja" junto al elemento mientras se arrastra.
  • Zona de destino (drop zone): el contenedor que recibe el elemento al soltarlo.

Las acciones son inmediatas y naturales: arrastras un elemento, lo sueltas, y la interfaz reacciona al instante.

Casos de uso modernos del Drag and Drop

  • Reordenar listas tipo Kanban o to-do list.
    • Lo uso habitualmente en mis cursos y libros, implementando un backend con Laravel o Django para persistir el nuevo orden en base de datos.
  • Construir interfaces estilo Trello o Gmail.
  • Subir archivos arrastrándolos directamente a un contenedor (sin <input type="file">).
  • Mover imágenes entre galerías.
  • Interfaces de backoffice con bloques reubicables, como editores de páginas o constructores de formularios.

Eventos del Drag and Drop en JavaScript: explicación clara y sin enredos

La API de arrastrar y soltar usa siete eventos clave —y eso es todo. Con la práctica los irás memorizando de forma natural. Veámoslos organizados según su rol:

  • Eventos del elemento arrastrado
    • dragstart: se dispara una sola vez cuando el usuario empieza a arrastrar. Aquí inicializas los datos con dataTransfer.setData().
    • drag: se dispara repetidamente mientras el elemento está siendo arrastrado. Útil para feedback visual en tiempo real.
    • dragend: se dispara cuando el usuario suelta el botón del ratón, independientemente de si el elemento llegó a su destino o no. Ideal para limpiar estilos.
  • Eventos de la zona de destino
    • dragenter: el elemento arrastrable entra en la zona de destino. Perfecto para destacar visualmente el contenedor.
    • dragover: se dispara continuamente mientras el elemento está encima de la zona. Es obligatorio llamar a event.preventDefault() aquí para que el evento drop pueda dispararse.
    • dragleave: el elemento arrastrable sale de la zona sin soltarse. Úsalo para revertir los estilos aplicados en dragenter.
    • drop: el elemento es soltado dentro de la zona. Aquí recuperas los datos con dataTransfer.getData() y ejecutas la lógica de negocio.
  • Errores típicos que rompen el DnD
    • No llamar a event.preventDefault() en dragover. Esto bloquea el drop completamente. Me pasó tantas veces que ya es un tic añadirlo.
    • Modificar el DOM dentro de dragover. Como este evento se dispara decenas de veces por segundo, cualquier manipulación del DOM en él afecta brutalmente al rendimiento.
    • No limpiar los estilos en dragleave o dragend. El resultado son zonas de destino que quedan "activas" visualmente aunque ya no haya nada arrastrándose.
    • En Firefox, es obligatorio llamar a event.dataTransfer.setData() en el evento dragstart, aunque sea con un valor vacío. Sin ello, el arrastre no funciona en ese navegador.

El Drag and Drop (Arrastrar y Soltar) es una característica que permite "agarrar" un objeto y arrastrarlo a una diferente localización. En HTML5, esto se logra de forma completamente nativa, sin depender de jQuery UI ni otras librerías.

Drag and drop ejemplo básico en HTML5

Uno de los grandes cambios que incorporó HTML5 fue precisamente la característica de arrastrar y soltar nativamente. Puede aplicarse a cualquier tipo de elemento del DOM: contenedores <div>, <textarea>, párrafos <p>, títulos, imágenes, etc.

Es una herramienta poderosísima que lleva la interacción con el usuario a otro nivel, y que resulta especialmente atractiva en combinación con las pantallas táctiles de tablets y smartphones.

Una aplicación debe ser intuitiva. ¿Y qué hay más intuitivo que arrastrar un "objeto" de una parte a otra?

¿En qué consiste exactamente la característica de arrastrar y soltar?

Con el Drag and Drop simplemente llevamos un elemento de A a B "arrastrándolo" de un lado a otro. El principio no es exclusivo de HTML: tecnologías como Android o iOS emplean exactamente el mismo concepto, lo que hace que los usuarios lo entiendan de forma inmediata.

Ahora bien, aunque el concepto es simple, la API de HTML5 expone una cantidad importante de métodos, eventos y propiedades que permiten explotar esta tecnología al máximo. No te preocupes: los irás viendo uno a uno con ejemplos concretos.

Empezando con el Drag and Drop en HTML5

En esta guía veremos las definiciones clave de cómo funciona esta tecnología, sus componentes, métodos, atributos y varios ejemplos muy prácticos que seguro te resultan útiles en el día a día.

Para crear elementos arrastrables ya no es necesario usar un plugin externo. Con HTML5 y JavaScript nativo podrás implementar Drag and Drop sobre cualquier elemento del DOM.

Eventos del Drag and Drop en JavaScript — Drag (elemento arrastrable)

Los elementos arrastrables producen tres eventos durante su ciclo de vida:

1.0 dragstart

Este evento se dispara en el momento en que el usuario empieza a arrastrar un elemento. Es aquí donde debes especificar qué estás arrastrando y establecer los valores correspondientes mediante el método setData(). Solo se invoca una vez por operación de arrastre.

Además, en este evento puedes aplicar un estilo visual personalizado al elemento mientras está siendo arrastrado, por ejemplo cambiando su opacidad o añadiendo una clase CSS.

2.0 drag

Este evento se dispara inmediatamente después de dragstart y continúa ejecutándose mientras el usuario mantiene el elemento agarrado. La frecuencia exacta con la que se invoca depende del navegador.

Este evento es útil para conocer en todo momento el estado del elemento arrastrable; por ejemplo, para rastrear su posición exacta en tiempo real.

3.0 dragend

Ocurre cuando el usuario termina de arrastrar el elemento, ya sea soltándolo dentro de un contenedor válido o simplemente liberando el botón del ratón en cualquier otro lugar. Solo se ejecuta una vez y es el lugar ideal para limpiar estilos o estados.

Los atributos HTML correspondientes a estos eventos son: ondragstart, ondrag y ondragend.

Eventos del Drag and Drop en JavaScript — Drop (zona de destino)

El contenedor o zona de destino produce cuatro eventos:

1.0 dragenter

Se dispara cuando el elemento arrastrable entra dentro de la zona de destino (drop zone), pero todavía no ha sido soltado.

Este evento es ideal para cambiar los estilos del contenedor y darle al usuario una señal visual de que puede soltar el elemento aquí.

En este evento también puedes inspeccionar los datos transferidos (dataTransfer) y el tipo de dato que fue inicializado mediante setData().

2.0 dragleave

Se dispara cuando un elemento arrastrable sale de la zona de destino sin haber sido soltado.

Este evento es perfecto para revertir cualquier estilo visual que hayas aplicado durante el dragenter.

3.0 dragover

Se dispara continuamente mientras el elemento arrastrable se mueve dentro del contenedor de destino, y solo se detiene cuando el elemento es soltado o sale de la zona. Su frecuencia de disparo depende del navegador.

Al igual que drag, es útil para rastrear la posición exacta del elemento dentro del contenedor. Recuerda siempre llamar a event.preventDefault() aquí; de lo contrario, el evento drop nunca se disparará.

4.0 drop

Se dispara cuando el elemento arrastrable es soltado dentro del contenedor. Es en este evento donde debes recoger la información transferida mediante el método getData() y ejecutar la lógica correspondiente.

Los atributos HTML correspondientes son: ondragenter, ondragleave, ondragover y ondrop.

Atributos del Drag and Drop en HTML5

Manejar los eventos no es suficiente por sí solo. Para que un elemento del DOM sea arrastrable, debes añadirle el atributo draggable="true".

Cualquier elemento del DOM puede hacerse arrastrable: <div>, <img>, <p>, <li>, etc.

Hasta aquí hemos visto qué eventos ocurren en el ciclo de vida del Drag and Drop y cómo interactúan los elementos arrastrables con sus contenedores. Pero queda una pregunta clave:

¿Cómo enviamos los datos asociados al elemento que se está arrastrando?

Es decir: ¿cómo se determina qué información "lleva consigo" el elemento durante el arrastre? Para eso existe el siguiente objeto.

El objeto dataTransfer en el Drag and Drop HTML5

Arrastrar y soltar elementos sin datos asociados tiene un uso limitado. El objeto dataTransfer es el cerebro de la operación: permite establecer la información en el evento dragstart() y recuperarla en el evento drop(). Veamos sus métodos más importantes:

.setData(formato, dato)

Con este método insertas la información que deseas guardar desde el elemento arrastrable. Debes especificar el tipo de dato (formato MIME) para garantizar compatibilidad entre navegadores:

  • Para texto plano: "text/plain".
  • Para una URL: "text/uri-list".
  • Para HTML estructurado: "text/html".

Debes establecer estos datos en el evento dragstart mediante event.dataTransfer.setData(type, data).

.getData(formato)

Devuelve la información previamente almacenada con setData(). Solo puede recuperarse con éxito en el evento drop, mediante event.dataTransfer.getData(type).

.clearData()

Limpia todos los datos establecidos por setData(formato, dato) mediante event.dataTransfer.clearData(type). Útil para resetear el estado entre operaciones.

setData / getData — ejemplo rápido

// En dragstart:
event.dataTransfer.setData("text/plain", elemento.id);

// En drop:
const id = event.dataTransfer.getData("text/plain");

MIME types soportados

  • "text/plain" — texto sin formato (el más compatible)
  • "text/html" — HTML estructurado
  • "text/uri-list" — URLs

Imagen fantasma personalizada

Por defecto, el navegador muestra una copia semitransparente del elemento mientras se arrastra. Puedes reemplazarla con una imagen personalizada:

event.dataTransfer.setDragImage(miImagen, 10, 10);

Los dos últimos parámetros definen el offset (en píxeles) del cursor respecto a la imagen.

Ejemplos prácticos paso a paso (código listo para usar)

Veamos una serie de ejemplos progresivos con los cuales podrás entender cómo usar Drag and Drop en HTML5 y adaptarlo a tus proyectos.

1.0 Ejemplo básico de Drag and Drop en HTML5 con el selector :after

Este es el ejemplo más básico: emplea los mínimos eventos necesarios para que el arrastre funcione. Es el punto de partida ideal antes de añadir complejidad.

Código completo (HTML y JavaScript)

function dragstart(caja, evento) {
    // Guardamos el ID del elemento a arrastrar
    event.dataTransfer.setData('Data', caja.id);
}
function drop(target, evento) {
    // Recuperamos el ID guardado en dragstart
    var caja = event.dataTransfer.getData('Data');
    // Movemos el elemento al nuevo contenedor
    target.appendChild(document.getElementById(caja));
}

Al iniciarse el arrastre (evento dragstart), indicamos qué datos transferir mediante setData(). Cuando el elemento es soltado dentro del contenedor, el evento drop recoge esa información con getData() y reinserta el nodo en el DOM.

Puedes ver el ejemplo completo en:

Como ves, usamos el selector CSS :after para variar dinámicamente el texto del elemento arrastrable (la caja) dependiendo de si está dentro o fuera del contenedor de destino.

Personalizar la zona de destino mediante CSS

Puedes dar feedback visual a la zona de destino combinando los eventos de JavaScript con clases CSS:

.drop-zone { transition: background 0.2s ease; }
.drop-zone.over { background: #e0f7fa; border: 2px dashed #0097a7; }

2.0 Ejemplo de Drag and Drop con todos los eventos en acción

Para entender cómo interactúan todos los eventos, este ejemplo extiende el anterior registrando todos los eventos soportados por la API y mostrando un mensaje en la consola del navegador por cada uno:

Código JavaScript completo

function dragstart(caja, event) {
    event.dataTransfer.setData('Data', caja.id);
}
function drag(target, event) {
    console.log("drag");
    return false;
}
function dragend(target, event) {
    console.log("dragend");
    return false;
}
function dragenter(target, event) {
    console.log("dragenter");
    return false;
}
function dragleave(target, event) {
    console.log("dragleave");
    return false;
}
function dragover(event) {
    console.log("dragover");
    event.preventDefault(); // Imprescindible para permitir el drop
    return false;
}
function drop(target, event) {
    var caja = event.dataTransfer.getData('Data');
    target.appendChild(document.getElementById(caja));
}

La lógica central es idéntica al ejemplo anterior. La diferencia es que ahora verás múltiples eventos ejecutándose en la Consola de Desarrollador (F12) según vayas posicionando el elemento arrastrable. Es una forma excelente de entender el orden y la frecuencia de disparo de cada evento.

Puedes ver el ejemplo completo en:

3.0 Arrastrando y soltando archivos desde el escritorio

Seguramente has usado más de una vez la opción de adjuntar archivos arrastrándolos directamente en Gmail. Con HTML5 esto es sorprendentemente fácil de replicar:

Drag and drop de archivos en Gmail

La clave está en que, al ocurrir el evento drop con un archivo del sistema operativo, el objeto event.dataTransfer.files contiene la lista de archivos arrastrados. Desde ahí puedes leerlos con la FileReader API o subirlos directamente al servidor vía fetch o XMLHttpRequest.

Código JavaScript completo

var MAX_BYTES = 102400; // 100 KB

function dragenter(event) {
    event.stopPropagation();
    event.preventDefault();
}
function dragover(event) {
    event.stopPropagation();
    event.preventDefault();
}
function drop(event) {
    console.log('drop', event);
    event.stopPropagation();
    event.preventDefault();

    var data = event.dataTransfer;
    var files = data.files;
    var file;
    var reader;

    for (var i = 0; i < files.length; i++) {
        file = files[i];
        reader = new FileReader();
        reader.onloadend = onFileLoaded;
        reader.readAsBinaryString(file);
    }
}
function onFileLoaded(event) {
    document.getElementById("resultado").value = event.currentTarget.result.substr(0, MAX_BYTES);
}

var contenedor = document.getElementById("contenedor");
contenedor.addEventListener("dragenter", dragenter, false);
contenedor.addEventListener("dragover", dragover, false);
contenedor.addEventListener("drop", drop, false);

La API de archivos en JavaScript es un tema en sí mismo; puedes ampliar información en el enlace al principio de este artículo.

Puedes ver el ejemplo completo en:

4.0 Cambiando el estilo de los elementos con los eventos del Drag and Drop

En este ejemplo vemos cómo variar dinámicamente los estilos de los elementos usando cada uno de los eventos del ciclo de vida del DnD. Es una técnica esencial para dar feedback visual claro al usuario:

Código JavaScript completo

function dragstart(caja, event) {
    document.getElementById(caja.id).className = "in";
    event.dataTransfer.setData('Data', caja.id);
}
function drag(caja, event) {
    return false;
}
function dragend(caja, event) {
    document.getElementById(caja.id).className = "out";
    return false;
}
function dragenter(target, event) {
    document.getElementById("contenedor").className = "inContainer";
    return false;
}
function dragleave(target, event) {
    document.getElementById("contenedor").className = "outContainer";
    return false;
}
function dragover(event) {
    event.preventDefault();
    return false;
}
function drop(target, event) {
    var caja = event.dataTransfer.getData('Data');
    document.getElementById("contenedor").className = "outContainer";
    target.appendChild(document.getElementById(caja));
}

Puedes ver el ejemplo completo en:

5.0 Drag and Drop básico con ES6+ (Arrow Functions)

La misma lógica usando sintaxis moderna con arrow functions. Este patrón es el más limpio para nuevos proyectos:

<div id="origen" draggable="true">A</div>
<div id="destino"></div>

<script>
origen.addEventListener("dragstart", e => {
    e.dataTransfer.setData("text/plain", e.target.id);
});

destino.addEventListener("dragover", e => e.preventDefault());

destino.addEventListener("drop", e => {
    e.preventDefault();
    const id = e.dataTransfer.getData("text/plain");
    destino.appendChild(document.getElementById(id));
});
</script>

6.0 Carga de archivos arrastrando desde el escritorio (versión moderna)

Este es uno de los casos más prácticos. Al ocurrir el evento drop con un archivo del sistema operativo, puedes obtenerlo y enviarlo al servidor mediante fetch:

contenedor.addEventListener("dragover", e => e.preventDefault());

contenedor.addEventListener("drop", e => {
    e.preventDefault();
    const archivos = e.dataTransfer.files;

    for (const file of archivos) {
        console.log("Archivo:", file.name, "Tamaño:", file.size, "bytes");
        // Aquí puedes validar tipo MIME y tamaño antes de subir
        // Subida del archivo vía fetch o XMLHttpRequest
    }
});

Cómo mejorar la experiencia visual del usuario

  • Estilos dinámicos con dragenter / dragleave
    • zona.addEventListener("dragenter", () => zona.classList.add("over"));
      zona.addEventListener("dragleave", () => zona.classList.remove("over"));
  • Indicadores visuales
    • Bordes punteados, resaltado de fondo, iconos de acción. Usa la clase .over para comunicar que la zona está lista para recibir el elemento.
  • Microinteracciones
    • Transiciones CSS de 100–200 ms hacen una diferencia enorme en la percepción de fluidez. Menos es más.

Drag and Drop en móvil y pantallas táctiles: limitaciones y soluciones

Aquí viene uno de los puntos más buscados en los datos de Search Console, y con razón: el DnD nativo de HTML5 no funciona igual en dispositivos móviles.

  • Limitaciones en móviles
    • Los eventos drag, dragover y drop no siempre se disparan en navegadores móviles.
    • La imagen fantasma generada por el navegador no aparece en iOS/Android.
      • El movimiento depende del sistema táctil del SO, no del navegador.
    • El atributo draggable="true" es ignorado por iOS Safari y muchos navegadores Android.
  • Soluciones
    • Combinar con eventos táctiles nativos: touchstart, touchmove, touchend.
    • Usar librerías especializadas que unifican ambos mundos: SortableJS, InteractJS, Dragula.
  • Mantener la experiencia fluida en ambos entornos es la clave de un buen diseño.

En proyectos donde uso Laravel o Django como backend, suelo combinar eventos táctiles con DnD nativo para mantener un mismo flujo de trabajo en escritorio y móvil sin duplicar lógica.

Para detectar si el usuario está en un dispositivo táctil y advertirle o adaptar la interfaz:

const isTouch = 'ontouchstart' in window;
if (isTouch) {
    console.warn("El DnD nativo tiene limitaciones en móviles. Considera usar SortableJS.");
}

Optimización y rendimiento en operaciones de arrastre

  • Minimizar repintados
    • Evita modificar estilos o el DOM directamente dentro de dragover. Dado que este evento puede dispararse decenas de veces por segundo, cualquier operación costosa aquí arruinará la experiencia.
  • Delegación de eventos
    • En lugar de asignar listeners individuales a cada elemento arrastrable, maneja los eventos desde un único listener en el contenedor padre. Esto es especialmente útil cuando los elementos se agregan dinámicamente al DOM.
  • Otras recomendaciones reales
    • Aplica throttling a callbacks muy frecuentes usando requestAnimationFrame.
    • Evita árboles DOM muy profundos o pesados dentro de los elementos arrastrables.
    • Usa transformaciones CSS (transform: translate()) para mover elementos visualmente en lugar de modificar sus propiedades de posición, ya que las transformaciones no generan reflow.

Seguridad y manejo seguro de datos en DnD

El objeto dataTransfer es potente, pero hay consideraciones de seguridad que no debes ignorar:

  • Sanitización de HTML
    • Si transfieres HTML mediante dataTransfer y lo insertas en el DOM con innerHTML, sanitiza siempre el contenido antes. Un atacante podría inyectar scripts maliciosos (XSS).
  • Prevención de fugas de datos
    • Nunca transfieras tokens de sesión, contraseñas, claves de API u otros datos sensibles a través de dataTransfer. Usa identificadores opacos o índices en su lugar.
  • DnD y subida de archivos
    • Siempre valida los archivos antes de procesarlos o enviarlos al servidor:
      • Tamaño: comprueba file.size antes de subir.
      • Extensión: filtra por extensión con una lista blanca.
      • Tipo MIME: valida file.type, aunque recuerda que el cliente puede manipularlo; la validación definitiva debe hacerse en el servidor.

En mis implementaciones de carga de archivos con Laravel, siempre valido el tamaño y el MIME type en el cliente para dar feedback inmediato, y vuelvo a validarlos en el servidor antes de persistir nada.

Conclusión

En esta guía sobre Drag and Drop con HTML5 vimos cómo crear tu propio sistema de arrastrar y soltar desde cero, repasando los siete eventos del ciclo de vida, el objeto dataTransfer, y una serie de ejemplos progresivos que cubren desde el caso más básico hasta la carga de archivos desde el escritorio.

La cantidad de eventos que maneja la API puede parecer intimidante al principio, pero en realidad es todo lo contrario: esa granularidad es lo que te permite tener control total sobre el estado del elemento arrastrable y del contenedor en cada momento, y personalizar la experiencia casi al 100%.

El Drag and Drop nativo en HTML5 y JavaScript sigue siendo una herramienta potente, flexible y sin dependencias externas. Con unos pocos eventos puedes construir interfaces avanzadas, desde tableros tipo Trello hasta módulos de carga de archivos. Y cuando el proyecto lo requiera —ya sea por necesitar soporte móvil robusto o reordenamiento complejo—, ya sabes cuándo dar el salto a una librería.

¿Cuándo conviene usar una librería en lugar de la API nativa?

  • Proyectos grandes o con muchos elementos arrastrables que cambian dinámicamente.
  • Reordenamiento complejo con animaciones de inserción entre elementos.
  • Necesidad de soporte móvil/táctil sin gestionar manualmente los eventos touchstart / touchmove.

Aprende ahora a copiar y pegar con solo JavaScript usando la API Clipboard.

Aprende Drag and Drop nativo con HTML5 y JavaScript: eventos, objeto dataTransfer, subida de archivos, solución al soporte móvil, evitar errores comunes con esta guía paso a paso y ejemplos reales.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.