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 condataTransfer.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 aevent.preventDefault()aquí para que el eventodroppueda dispararse.dragleave: el elemento arrastrable sale de la zona sin soltarse. Úsalo para revertir los estilos aplicados endragenter.drop: el elemento es soltado dentro de la zona. Aquí recuperas los datos condataTransfer.getData()y ejecutas la lógica de negocio.
- Errores típicos que rompen el DnD
- No llamar a
event.preventDefault()endragover. Esto bloquea eldropcompletamente. 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
dragleaveodragend. 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 eventodragstart, aunque sea con un valor vacío. Sin ello, el arrastre no funciona en ese navegador.
- No llamar a
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.

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.
¿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.
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.
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.
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.
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.
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.
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.
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".
<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:
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:

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/dragleavezona.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
.overpara comunicar que la zona está lista para recibir el elemento.
- Bordes punteados, resaltado de fondo, iconos de acción. Usa la clase
- 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,dragoverydropno 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.
- Los eventos
- Soluciones
- Combinar con eventos táctiles nativos:
touchstart,touchmove,touchend. - Usar librerías especializadas que unifican ambos mundos: SortableJS, InteractJS, Dragula.
- Combinar con eventos táctiles nativos:
- 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.
- Evita modificar estilos o el DOM directamente dentro de
- 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.
- Aplica throttling a callbacks muy frecuentes usando
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
dataTransfery lo insertas en el DOM coninnerHTML, sanitiza siempre el contenido antes. Un atacante podría inyectar scripts maliciosos (XSS).
- Si transfieres HTML mediante
- 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.
- Nunca transfieras tokens de sesión, contraseñas, claves de API u otros datos sensibles a través de
- DnD y subida de archivos
- Siempre valida los archivos antes de procesarlos o enviarlos al servidor:
- Tamaño: comprueba
file.sizeantes 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.
- Tamaño: comprueba
- Siempre valida los archivos antes de procesarlos o enviarlos al 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.