La API de arrastrar y soltar de HTML5

En esta publicación, se explican los conceptos básicos de arrastrar y soltar.

Cómo crear contenido arrastrable

En la mayoría de los navegadores, las selecciones de texto, las imágenes y los vínculos son arrastrables de forma predeterminada. Por ejemplo, si arrastras un vínculo en una página web, verás un pequeño cuadro con un título y una URL que puedes soltar en la barra de direcciones o en el escritorio para crear un acceso directo o navegar al vínculo. Para que otros tipos de contenido sean arrastrables, debes usar las APIs de arrastrar y soltar de HTML5.

Para hacer que un objeto sea arrastrable, configura draggable=true en ese elemento. Se puede habilitar casi cualquier acción para arrastrar, por ejemplo, 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 presentaron con la cuadrícula de CSS. El lenguaje de marcado básico para las columnas tiene el siguiente aspecto, con el atributo draggable de cada columna establecido en 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>

Este es el CSS para los elementos del contenedor y del cuadro. El único CSS relacionado con el componente de arrastre es la propiedad cursor: move. El resto del código controla el diseño y el estilo del contenedor y los elementos 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 sucede nada más. Para agregar un comportamiento, debes usar la API de JavaScript.

Cómo escuchar 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 se inicia el arrastre), la carga útil de datos (lo que se arrastra) y un destino (un área para capturar la acción de soltar). El elemento fuente puede ser casi cualquier tipo de elemento. El objetivo es la zona de drop o el conjunto de zonas que acepta los datos que el usuario intenta descartar. No todos los elementos pueden ser objetivos. Por ejemplo, el destino no puede ser una imagen.

Iniciar y finalizar una secuencia de arrastre

Después de definir los atributos draggable="true" en tu contenido, adjunta un controlador de eventos dragstart para iniciar la secuencia de arrastre de cada columna.

Este código establece la opacidad de la columna en 40% cuando el usuario comienza a arrastrarla y, luego, la devuelve al 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);
});

El resultado se puede ver 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, establecer this.style.opacity en 40% le brinda al usuario comentarios visuales de que ese elemento es la selección actual que se está moviendo. Cuando sueltas el elemento, el elemento de origen vuelve al 100% de opacidad, aunque todavía no hayas definido el comportamiento de la acción de soltar.

Agrega señales visuales adicionales

Para ayudar al usuario a entender cómo interactuar con tu interfaz, usa los controladores de eventos dragenter, dragover y dragleave. En este ejemplo, las columnas son objetivos para soltar, además de ser arrastrables. Ayuda al usuario a comprender esto marcando el borde con puntos cuando sostengan un elemento arrastrado sobre una columna. Por ejemplo, en tu CSS, puedes crear una clase over para elementos que son destinos para soltar:

.box.over {
  border: 3px dotted #666;
}

Luego, en tu JavaScript, configura los controladores de eventos, agrega la clase over cuando se arrastra la columna y quítala cuando el elemento arrastrado se vaya. 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 abarcar en este código:

  • La acción predeterminada para el evento dragover es establecer la propiedad dataTransfer.dropEffect en "none". La propiedad dropEffect se tratará más adelante en esta página. Por ahora, solo debes saber que evita que se active el evento drop. Para anular este comportamiento, llama a e.preventDefault(). Otra práctica recomendada es mostrar false en ese mismo controlador.

  • El controlador de eventos dragenter se usa para activar o desactivar la clase over, en lugar de dragover. Si usas dragover, el evento se activa repetidamente mientras el usuario mantiene el elemento arrastrado sobre una columna, lo que provoca que la clase de CSS se active de forma repetitiva. Esto hace que el navegador realice una gran cantidad de trabajo de renderización innecesaria, lo que puede afectar la experiencia del usuario. Te recomendamos que minimices los reintentos de dibujo. Si necesitas usar dragover, considera regular o anular el objeto de escucha de eventos.

Completa el proceso de lanzamiento

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 bajas, que suelen 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 nuevo controlador junto con los otros controladores:

  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 baja en la nueva ubicación. Para que esto 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 configurar el tipo de MIME y la carga útil de datos del objeto.

En este ejemplo, permitiremos que los usuarios reorganicen el orden de las columnas. Para ello, primero debes almacenar el código HTML del elemento fuente cuando se inicia 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, para procesar la eliminación de columnas, configura el HTML de la columna de origen con el HTML de la columna de destino en la que descartaste los datos. Esto incluye verificar que el usuario no vuelva a la misma columna de la que se arrastró.

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 en la parte superior de la columna B y observa cómo cambian de lugar:

Más propiedades de arrastre

El objeto dataTransfer expone propiedades para proporcionar información visual al usuario durante el proceso de arrastre y controlar cómo responde cada destino para soltar 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 inicializar dropEffect durante los eventos dragenter y dragover. La propiedad puede tener los siguientes valores: none, copy, copyLink, copyMove, link, linkMove, move, all y uninitialized.
  • dataTransfer.dropEffect controla los comentarios que recibe el usuario durante los eventos dragenter y dragover. Cuando el usuario mantiene el puntero sobre un elemento de destino, el cursor del navegador indica el tipo de operación que se realizará, como una copia o un movimiento. El efecto puede tener uno de los siguientes valores: none, copy, link o move.
  • e.dataTransfer.setDragImage(imgElement, x, y) significa que, en lugar de usar los comentarios de "imagen fantasma" predeterminados del navegador, puedes configurar un ícono de arrastre.

Carga de archivo

En este ejemplo simple, se usa una columna como origen de arrastre y destino de arrastre. Esto puede suceder en una IU que le solicita al usuario que reorganice los elementos. En algunas situaciones, el destino de arrastre y la fuente 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 hasta un destino.

La función Arrastrar y soltar se usa con frecuencia para permitir que los usuarios arrastren elementos desde su escritorio a una aplicación. La diferencia principal se encuentra en el controlador drop. En lugar de usar dataTransfer.getData() para acceder a los archivos, sus datos se encuentran 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.
  }
}

Puedes obtener más información al respecto en Arrastrar y soltar de forma personalizada.

Más recursos