En esta publicación, se explican los conceptos básicos de la función arrastrar y soltar.
Crea contenido que se pueda arrastrar
En la mayoría de los navegadores, las selecciones de texto, las imágenes y los vínculos pueden arrastrarse de forma predeterminada. Por ejemplo, si arrastras un vínculo en una página web, verás un cuadro pequeño con un título y una URL que puedes soltar en la barra de direcciones o en el escritorio para crear un atajo o navegar al vínculo. Para que otros tipos de contenido sean arrastrables, debes usar las APIs de arrastrar y soltar de HTML5.
Para que un objeto sea arrastrable, configura draggable=true
en ese elemento. Puedes arrastrar casi cualquier elemento, como imágenes, archivos, vínculos, archivos o cualquier lenguaje de marcado de tu página.
En el siguiente ejemplo, se crea una interfaz para reorganizar las columnas que se diseñaron con CSS Grid. El lenguaje de marcado básico de las columnas se ve de la siguiente manera, con el atributo draggable
para cada columna configurado como true
:
<div class="container">
<div draggable="true" class="box">A</div>
<div draggable="true" class="box">B</div>
<div draggable="true" class="box">C</div>
</div>
A continuación, se muestra el código CSS para los elementos del contenedor y el cuadro. El único CSS relacionado con la función de arrastrar es la propiedad cursor: move
. El resto del código controla el diseño y el estilo de los elementos del contenedor y del cuadro.
.container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.box {
border: 3px solid #666;
background-color: #ddd;
border-radius: .5em;
padding: 10px;
cursor: move;
}
En este punto, puedes arrastrar los elementos, pero no sucederá nada más. Para agregar un comportamiento, debes usar la API de JavaScript.
Cómo detectar eventos de arrastre
Para supervisar el proceso de arrastre, puedes escuchar cualquiera de los siguientes eventos:
Para controlar el flujo de arrastre, necesitas algún tipo de elemento de origen (donde comienza el arrastre), la carga útil de datos (lo que se arrastra) y un destino (un área para capturar la caída). El elemento fuente puede ser casi cualquier tipo de elemento. El objetivo es la zona de caída o el conjunto de zonas de caída que acepta los datos que el usuario intenta colocar. No todos los elementos pueden ser objetivos. Por ejemplo, el objetivo no puede ser una imagen.
Cómo iniciar y finalizar una secuencia de arrastre
Después de definir los atributos draggable="true"
en tu contenido, adjunta un controlador de evento dragstart
para iniciar la secuencia de arrastre de cada columna.
Este código establece la opacidad de la columna en un 40% cuando el usuario comienza a arrastrarla y, luego, la vuelve a establecer en un 100% cuando finaliza el evento de arrastre.
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
}
let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragend', handleDragEnd);
});
Puedes ver el resultado en la siguiente demostración de Glitch. Arrastra un elemento, y su
opacidad cambia. Debido a que el elemento de origen tiene el evento dragstart
, configurar this.style.opacity
en un 40% le brinda al usuario una respuesta visual de que ese elemento es la selección actual que se está moviendo. Cuando sueltas el elemento, el elemento fuente vuelve a tener una opacidad del 100%, aunque aún no hayas definido el comportamiento de soltar.
Agrega indicadores visuales adicionales
Para ayudar al usuario a comprender cómo interactuar con tu interfaz, usa los controladores de eventos dragenter
, dragover
y dragleave
. En este ejemplo, las columnas son destinos de caída, además de ser arrastrables. Para ayudar al usuario a comprender esto, usa el borde discontinuo cuando sostiene un elemento arrastrado sobre una columna. Por ejemplo, en tu CSS, puedes crear una clase over
para los elementos que son destinos de caída:
.box.over {
border: 3px dotted #666;
}
Luego, en tu código JavaScript, configura los controladores de eventos, agrega la clase over
cuando se arrastre la columna y quítala cuando se quite el elemento arrastrado. En el controlador dragend
, también nos aseguramos de quitar las clases al final del arrastre.
document.addEventListener('DOMContentLoaded', (event) => {
function handleDragStart(e) {
this.style.opacity = '0.4';
}
function handleDragEnd(e) {
this.style.opacity = '1';
items.forEach(function (item) {
item.classList.remove('over');
});
}
function handleDragOver(e) {
e.preventDefault();
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
});
Hay un par de puntos que vale la pena cubrir en este código:
La acción predeterminada para el evento
dragover
es establecer la propiedaddataTransfer.dropEffect
en"none"
. La propiedaddropEffect
se analiza más adelante en esta página. Por ahora, solo ten en cuenta que evita que se active el eventodrop
. Para anular este comportamiento, llama ae.preventDefault()
. Otra práctica recomendada es mostrarfalse
en ese mismo controlador.El controlador de eventos
dragenter
se usa para activar o desactivar la claseover
en lugar dedragover
. Si usasdragover
, el evento se activa de forma reiterada mientras el usuario mantiene el elemento arrastrado sobre una columna, lo que hace que la clase CSS se active de forma reiterada. Esto hace que el navegador realice mucho trabajo de renderización innecesario, lo que puede afectar la experiencia del usuario. Te recomendamos que minimices los dibujos nuevos y, si necesitas usardragover
, considera restringir o anular el objeto de escucha de eventos.
Completa la bajada
Para procesar la acción de soltar, agrega un objeto de escucha de eventos para el evento drop
. En el controlador drop
, deberás evitar el comportamiento predeterminado del navegador para las caídas, que suele ser algún tipo de redireccionamiento molesto. Para ello, llama a e.stopPropagation()
.
function handleDrop(e) {
e.stopPropagation(); // stops the browser from redirecting.
return false;
}
Asegúrate de registrar el controlador nuevo junto con los demás:
let items = document.querySelectorAll('.container .box');
items.forEach(function(item) {
item.addEventListener('dragstart', handleDragStart);
item.addEventListener('dragover', handleDragOver);
item.addEventListener('dragenter', handleDragEnter);
item.addEventListener('dragleave', handleDragLeave);
item.addEventListener('dragend', handleDragEnd);
item.addEventListener('drop', handleDrop);
});
Si ejecutas el código en este punto, el elemento no cae en la nueva ubicación. Para que eso suceda, usa el objeto DataTransfer
.
La propiedad dataTransfer
contiene los datos enviados en una acción de arrastre. dataTransfer
se establece en el evento dragstart
y se lee o se controla en el evento de soltar. Llamar a e.dataTransfer.setData(mimeType, dataPayload)
te permite establecer el tipo de MIME y la carga útil de datos del objeto.
En este ejemplo, permitiremos que los usuarios reordenen el orden de las columnas. Para ello, primero debes almacenar el HTML del elemento fuente cuando comienza el arrastre:
function handleDragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
En el evento drop
, procesas la caída de la columna configurando el HTML de la columna de origen en el HTML de la columna de destino en la que colocaste los datos. Esto incluye verificar que el usuario no vuelva a la misma columna desde la que arrastre.
function handleDrop(e) {
e.stopPropagation();
if (dragSrcEl !== this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
Puedes ver el resultado en la siguiente demostración. Para que esto funcione, necesitarás un navegador de escritorio. La API de arrastrar y soltar no es compatible con dispositivos móviles. Arrastra y suelta la columna A sobre la columna B y observa cómo cambian de lugar:
Más propiedades de arrastre
El objeto dataTransfer
expone propiedades para proporcionar comentarios visuales al usuario durante el proceso de arrastre y controlar cómo cada destino para soltar responde a un tipo de datos en particular.
dataTransfer.effectAllowed
restringe el "tipo de arrastre" que el usuario puede realizar en el elemento. Se usa en el modelo de procesamiento de arrastrar y soltar para inicializardropEffect
durante los eventosdragenter
ydragover
. La propiedad puede tener los siguientes valores:none
,copy
,copyLink
,copyMove
,link
,linkMove
,move
,all
yuninitialized
.dataTransfer.dropEffect
controla los comentarios que recibe el usuario durante los eventosdragenter
ydragover
. Cuando el usuario mantiene el puntero sobre un elemento de destino, el cursor del navegador indica qué tipo de operación se llevará a cabo, como una copia o un movimiento. El efecto puede tener uno de los siguientes valores:none
,copy
,link
omove
.e.dataTransfer.setDragImage(imgElement, x, y)
significa que, en lugar de usar los comentarios predeterminados de "imagen fantasma" del navegador, puedes configurar un ícono de arrastre.
Subir archivo
En este ejemplo simple, se usa una columna como fuente y destino de arrastre. Esto puede ocurrir en una IU que le pide al usuario que reordene los elementos. En algunas situaciones, el destino de arrastre y el origen pueden ser tipos de elementos diferentes, como en una interfaz en la que el usuario necesita seleccionar una imagen como la imagen principal de un producto arrastrando la imagen seleccionada sobre un objetivo.
La función de arrastrar y soltar se usa con frecuencia para permitir que los usuarios arrastren elementos desde su escritorio a una aplicación. La diferencia principal está en tu controlador drop
. En lugar de usar
dataTransfer.getData()
para acceder a los archivos, sus datos se contienen en la
propiedad dataTransfer.files
:
function handleDrop(e) {
e.stopPropagation(); // Stops some browsers from redirecting.
e.preventDefault();
var files = e.dataTransfer.files;
for (var i = 0, f; (f = files[i]); i++) {
// Read the File objects in this FileList.
}
}
Para obtener más información al respecto, consulta Cómo arrastrar y soltar personalizados.