Muchas veces, nos habremos dado cuenta que aunque nuestra página realiza los objetivos que requiere, su utilización no resulta cómoda o intuitiva para los usuarios. Muchas veces, esto puede ocurrir porque la interfaz carece de un sistema de Drag and Drop.
¿Qué es el Drag and Drop?
Se conoce como Drag and Drop al hecho de arrastrar (Drag) y soltar (Drop) un elemento (draggable) sobre otro elemento (drop zone) en una interfaz de usuario. Ejemplos clásicos son cuando puedes arrastrar y soltar una imagen en lugar de pulsar el botón y buscar en las carpetas de tu sistema.
Algunos elementos como el botón de subir imágenes ya tienen drag and drop integrado (sobre el propio botón), pero esta implementación depende del navegador y el sistema operativo, y no es personalizable. Si queremos hacer algo más específico y bonito, tendremos que utilizar esta API.
El atributo draggable
Para empezar, tenemos que saber que existe un atributo HTML draggable
que debemos incluir en nuestro elemento HTML para convertirlo en un elemento que pueda arrastrar. El atributo draggable
debe tener el valor true
:
Atributo | Descripción |
---|---|
draggable | Con el valor true , convierte el elemento en arrastrable. |
Por defecto, algunos elementos como las imágenes <img>
ya son arrastrables y no requieren este atributo, sin embargo, otras como un simple <div>
no lo son. Observa el siguiente ejemplo:
<div class="container">
<img src="monoculo.gif" alt="ManzDev Monóculo">
<div class="box no-draggable"></div>
<div class="box" draggable="true"></div>
</div>
.container {
display: flex;
gap: 1rem;
}
.box {
width: 128px;
height: 128px;
background: indigo;
}
.no-draggable {
background: darkred;
}
El elemento rojo no es arrastrable, sin embargo el elemento morado si lo es.
Eventos Drag & Drop
Para personalizar nuestro sistema Drag and Drop necesitamos utilizar ciertos eventos de Javascript. Echemos un vistazo. Primero hablaremos de los eventos de arrastre (Drag), luego de los eventos de soltar (Drop) y por último comentaremos el objeto .dataTransfer
.
Eventos Drag
Podemos empezar con los eventos dragstart
, dragend
y drag
, tres eventos centrados en el elemento que arrastramos, es decir, en la parte en la que hacemos «Drag»:
Evento | Descripción |
---|---|
dragstart | Evento que se dispara cuando empiezas a arrastrar el elemento. |
dragend | Evento que se dispara cuando terminas de arrastrar el elemento. |
drag | Evento que se dispara continuamente mientras arrastras el elemento. ⚠️ Se ejecuta continuamente. |
Observa este fragmento de código Javascript, donde utilizamos estos 3 eventos para encender las cajas de la derecha de la demo. Ten en cuenta que los listeners se hacen sobre dragItem
, el elemento que arrastras. Al arrastrar la imagen y moverla, comprobarás que se encienden los eventos que se disparan:
const dragItem = document.querySelector(".drag-item");
const [start, move, end] = document.querySelectorAll(".badges div");
const setBlink = (element) => {
element.classList.add("on");
setTimeout(() => element.classList.remove("on"), 200);
}
dragItem.addEventListener("dragstart", () => setBlink(start));
dragItem.addEventListener("dragend", () => setBlink(end));
dragItem.addEventListener("drag", () => setBlink(move));
.container {
display: flex;
align-items: center;
gap: 1rem;
}
.badges {
display: flex;
gap: 2rem;
& div {
display: grid;
border: 1px solid #aaa;
padding: 1rem;
height: fit-content;
&.on {
background: orangered;
border-color: orangered;
color: #fff;
}
}
}
<div class="container">
<img class="drag-item" src="monoculo.gif" alt="ManzDev Monóculo">
<div class="badges">
<div>Start</div>
<div>Move</div>
<div>End</div>
</div>
</div>
El evento
drag
es un evento que se llama muchas veces por segundo. Intenta siempre que el código ejecutado endrag
sea breve, extremandamente rápido y ligero o tenga condicionales para evitar ejecutarse múltiples veces. De lo contrario podrías experimentar lag, bloqueos o retardos.
Eventos Drop
Ahora, centrémonos en la parte en la que hacemos «Drop», y soltamos el elemento en otro elemento. Para ello, vamos a utilizar los eventos dragenter
, dragleave
, dragover
y drop
:
Evento | Descripción |
---|---|
dragenter | Hemos entrado en una zona para soltar arrastrando el elemento. |
dragleave | Hemos salido de una zona para soltar arrastrando el elemento. |
dragover | Nos estamos moviendo en una zona para soltar. ⚠️ Se ejecuta continuamente. |
drop | Hemos soltado el elemento en una zona para soltar. |
Ten muy en cuenta que estamos escuchando con los listeners en dropZone
, el elemento donde sueltas. En este caso, debemos mover la imagen sobre el bloque que indica «Drop here». Estaremos detectando cuando un elemento es arrastrado hasta esa sección, cuando entras, cuando sales, etc.
const dragItem = document.querySelector(".drag-item");
const dropZone = document.querySelector(".drop-zone");
const [enter, leave, over, drop] = document.querySelectorAll(".badges div");
const setBlink = (element) => {
element.classList.add("on");
setTimeout(() => element.classList.remove("on"), 200);
}
dropZone.addEventListener("dragenter", (ev) => {
dropZone.classList.add("selected");
setBlink(enter);
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("selected");
setBlink(leave);
});
dropZone.addEventListener("dragover", (ev) => {
ev.preventDefault();
setBlink(over);
});
dropZone.addEventListener("drop", (ev) => {
setBlink(drop);
});
.container {
display: flex;
gap: 1rem;
}
.drop-zone {
border: 2px dotted #777;
width: 128px;
height: 128px;
display: grid;
place-items: center;
&.selected {
border: 2px solid orangered;
background: #f9dfb7;
}
}
.badges {
display: flex;
gap: 2rem;
& div {
display: grid;
border: 1px solid #aaa;
padding: 1rem;
height: fit-content;
&.on {
background: indigo;
border-color: indigo;
color: #fff;
}
}
}
<div class="container">
<img class="drag-item" src="monoculo.gif" alt="ManzDev Monóculo" draggable="true">
<div class="drop-zone">Drop here</div>
<div class="badges">
<div>Enter</div>
<div>Leave</div>
<div>Over</div>
<div>Drop</div>
</div>
</div>
Con el evento
dragover
ocurre lo mismo que con el eventodrag
. Intenta que sea muy ligero. Además, debes utilizar un.preventDefault()
para evitar ocultar la activación del eventodrop
.
El objeto dataTransfer
Sin embargo, hasta ahora tenemos forma de detectar cuando ocurren los eventos, pero no la información asociada a ellos, como por ejemplo, que elemento estamos arrastrando o donde lo hemos soltado. Para ello, vamos a utilizar la propiedad ev.dataTransfer
que ocurre en nuestros eventos de Drag and Drop.
Método | Descripción |
---|---|
getData(type) | Obtiene el valor guardado con setData() . Tipos: text/plain o text/uri-list . |
setData(type, value) | Guarda un valor en dataTransfer , pasándole el tipo y el valor a guardar. |
clearData(type) | Elimina los datos guardados en el dataTransfer . |
setDragImage(image, x, y) | Muestra una imagen traslúcida al arrastrar el elemento, desplazándola en x e y . |
En primer lugar, observa que hemos creado un <div>
con clase place
que es donde está ubicada la imagen original. Luego, le hemos añadido a la imagen un id
para identificarlo de forma única. Ahora, vamos a mover la imagen a la zona punteada:
const dragItem = document.querySelector(".drag-item");
const dropZone = document.querySelector(".drop-zone");
const honkImage = document.createElement("img");
honkImage.src = "honk.gif";
dragItem.addEventListener("dragstart", (ev) => {
ev.dataTransfer.setData("text/plain", dragItem.id);
ev.dataTransfer.setDragImage(honkImage, 0, 0);
});
dropZone.addEventListener("dragover", (ev) => ev.preventDefault());
dropZone.addEventListener("drop", (ev) => {
const id = ev.dataTransfer.getData("text/plain");
const item = document.querySelector("#" + id);
dropZone.append(item);
});
.container {
display: flex;
gap: 1rem;
}
.place {
width: 200px;
height: 200px;
}
.drop-zone {
border: 2px dotted #777;
width: 128px;
height: 128px;
display: grid;
place-items: center;
& img {
width: 100%;
height: 100%;
object-fit: cover;
}
&.selected {
border: 2px solid orangered;
background: #f9dfb7;
}
}
<div class="container">
<div class="place">
<img class="drag-item" id="item" src="monoculo.gif" alt="ManzDev Monóculo">
</div>
<div class="drop-zone"></div>
</div>
En este fragmento de código, en el evento dragstart
, utilizamos el método setData()
para pasar el id
del elemento que hemos empezado a arrastrar. Luego, en el evento drop
, obtenemos ese id
, lo buscamos en el DOM, y como es único, obtendremos el elemento HTML que hemos soltado. Además, lo que hacemos es añadirlo a la zona drop donde lo hemos soltado, y como es una referencia al original, lo que hará el navegador es moverlo desde su posición inicial.
Observa también, que con el método setDragImage()
hemos indicado una imagen para mostrar mientras se arrastra, y dar feedback visual al usuario.
Librerías de Drag and Drop
En algunos casos, nos puede interesar no ir a tan bajo nivel y priorizar nuestro tiempo de desarrollo sobre los inconvenientes de utilizar una librería. Una librería siempre tardará más en descargarse, parsearse y procesarse, y puede que sea más difícil de mantener en el futuro, sin embargo, a veces puede interesar por la velocidad de implementación que representa.
Algunas librerías Javascript interesantes para hacer Drag and Drop serían las siguientes:
Librería | Descripción |
---|---|
Drag & Drop formKit | Librería moderna, ligera que utiliza la API estándar. |
Draggable | Desarrollada originalmente por Shopify, mantenida actualmente por la comunidad. |
Sortable | Crea listas reordenables con esta librería. |
Dragula | Librería tradicional de Drag and Drop. |