Arrastrar y soltar en Vanilla JavaScript

En esta guía práctica, aprenda cómo implementar la funcionalidad de arrastrar y soltar en JavaScript estándar, ¡así como la biblioteca Sortable JS!

Introducción

El acto de seleccionar un elemento o fragmento de texto, moverlo (arrastrarlo) y luego colocarlo (soltarlo) en otra ubicación se describe como funcionalidad de arrastrar y soltar.

La mayoría de los navegadores hacen que las selecciones de texto, las imágenes y los enlaces se puedan arrastrar de forma predeterminada. Por ejemplo, si arrastra imágenes o logotipos basados ​​en imágenes en cualquier sitio web, aparecerá una "imagen fantasma" (esto no funciona para SVG, ya que esas no son imágenes).

{.icon aria-hidden=“true”}

Nota: Para hacer que otros tipos de contenido se puedan arrastrar, debe usar las API de arrastrar y soltar (DnD) HTML5 o una biblioteca de JavaScript externa.

En esta guía práctica, veremos cómo implementar arrastrar y soltar en JavaScript utilizando tanto la API de arrastrar y soltar (DnD) como una biblioteca de JavaScript externa, [ordenable](https: //ordenablejs.github.io/Ordenable/).

El tablero Kanban enormemente popular, Trello, hace uso de arrastrar y soltar para facilitar los movimientos de tarjetas de una lista a otra. En esta guía, construiremos algo muy similar.

Uso de la API de arrastrar y soltar de HTML5+

Para implementar la funcionalidad de arrastrar y soltar en HTML4 convencional, los desarrolladores tuvieron que utilizar una programación de JavaScript difícil u otros marcos de trabajo de JavaScript como jQuery, etc., pero HTML 5 introdujo una API de arrastrar y soltar (DnD) que brinda soporte DnD nativo al navegador, lo que lo hace mucho más fácil de codificar!

Todos los principales navegadores, incluidos Chrome, Firefox 3.5 y Safari 4, admiten este HTML 5 DnD.

Podemos hacer que prácticamente cualquier elemento de nuestro sitio web se pueda arrastrar usando la API. Con un mouse, el usuario puede seleccionar elementos que se pueden arrastrar, arrastrarlos a un elemento soltable y luego soltarlos soltando el botón del mouse. Esto hace uso tanto del paradigma de eventos DOM como de los eventos de arrastrar y soltar.

{.icon aria-hidden=“true”}

Nota: Se activan varios tipos de eventos durante las operaciones de arrastre, y ciertos eventos, como los eventos drag y dragover, pueden activarse varias veces.

Eventos de arrastrar y soltar

Se activan una serie de eventos en varias fases del procedimiento de arrastrar y soltar:

  • arrastre: cuando el usuario comienza a arrastrar el elemento, se produce este evento.
  • dragenter: cuando se mueve el mouse sobre el elemento de destino por primera vez al arrastrar, se activa este evento.
  • dragover: cuando se produce un arrastre, este evento se activa cuando se arrastra el mouse sobre un elemento. El proceso que ocurre durante un oyente suele ser el mismo que el evento dragenter.
  • dragleave: cuando el mouse abandona un elemento mientras lo arrastra, se activa este evento.
  • arrastrar: cuando se mueve el mouse mientras se arrastra el elemento, se activa este evento.
  • soltar: Al finalizar la operación de arrastre, el evento soltar se activa en el elemento donde ocurrió la caída. Un oyente estaría a cargo de obtener los datos arrastrados y colocarlos en el lugar de colocación.
  • dragend: Cuando el usuario suelta el botón del mouse mientras arrastra un elemento, ocurre este evento.

Comenzando

¡Construyamos una copia simple de un tablero de Trello! El resultado se verá algo así como estas líneas:

trello kanban board clone with vanilla javascript

Crear el proyecto y la marca inicial {#crear el proyecto y la marca inicial}

Vamos a crear la estructura básica en HTML: un “contenedor” con varios elementos de “columna” que actúan como listas de tareas. Digamos que la primera lista, correspondiente a la columna "Todas las tareas", tiene todas las tareas inicialmente, que podremos arrastrar y soltar en otras columnas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="container">
    <div class="column">
        <h1>All Tasks</h1>
        <div class="item">Wash Clothes</div>
        <div class="item">Meeting at 9AM</div>
        <div class="item">Fix workshop</div>
        <div class="item">Visit the zoo</div>
    </div>
    <div class="column">
        <h1>In progress</h1>
    </div>
    <div class="column">
        <h1>Paused</h1>
    </div>
    <div class="column">
        <h1>Under Review</h1>
    </div>
    <div class="column">
        <h1>Completed</h1>
    </div>
</div>

Agreguemos un estilo rudimentario a los contenedores, columnas y elementos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.container{
    font-family: "Trebuchet MS", sans-serif;
    display: flex;
    gap: 30px;
}
.column{
    flex-basis: 20%;
    background: #ddd;
    min-height: 90vh;
    padding: 20px;
    border-radius: 10px;
}
.column h1{
    text-align: center;
    font-size: 22px;
}
.item{
    background: #fff;
    margin: 20px;
    padding: 20px;
    border-radius: 3px;
    cursor: pointer;
}
.invisible{
    display: none;
}

La página debería tener un aspecto similar a este:

vanilla js kanban board

Haciendo que un objeto se pueda arrastrar

Sin embargo, estos objetos aún no se pueden arrastrar. ¡Están ahí! Para hacer que un objeto se pueda arrastrar, establecemos su atributo arrastrable en verdadero. ¡Se puede arrastrar cualquier cosa en su sitio web, incluidas fotos, archivos, enlaces y archivos!

Establezcamos draggable="true" en nuestros elementos item:

1
2
3
4
5
6
7
<div class="column">
    <h1>All Tasks</h1>
    <div class="item" draggable="true">Wash Clothes</div>
    <div class="item" draggable="true">Meeting at 9AM</div>
    <div class="item" draggable="true">Fix workshop</div>
    <div class="item" draggable="true">Visit the zoo</div>
</div>

Ahora que los elementos se pueden arrastrar, ¡pueden emitir eventos de arrastre! Configuremos detectores de eventos para que los detecten y reaccionen a los eventos.

Gestión de eventos de arrastrar y soltar con JavaScript

Reunamos todos los elementos y columnas en los que queremos implementar arrastrar y soltar. ¡Podemos recopilarlos fácilmente usando los selectores DOM document.querySelectorAll()! Esto generará una matriz NodeList, que podemos recorrer para operar con cada elemento/columna individual:

1
2
const items = document.querySelectorAll('.item')
const columns = document.querySelectorAll('.column')

Naturalmente, si no tiene una lista de elementos con los que trabajar, ¡puede seleccionarlos individualmente!

Recorramos los elementos y agreguemos un detector de eventos a cada uno. Agregaremos un detector de eventos para los eventos dragstart y dragend, y las funciones que se ejecutarán cuando se activen:

1
2
3
4
items.forEach(item => {
    item.addEventListener('dragstart', dragStart)
    item.addEventListener('dragend', dragEnd)
});

dragStart() se ejecutará en cada evento 'dragstart', y dragEnd() se ejecutará en cada evento 'dragend'.

{.icon aria-hidden=“true”}

Nota: Estas funciones se pueden usar para agregar estilo para una mejor interactividad visual cuando los usuarios arrastran un elemento en particular y lo sueltan, como una animación fluida de la tarjeta que está moviendo.

Probemos la funcionalidad simplemente registrando mensajes:

1
2
3
4
5
6
function dragStart() {
    console.log('drag started');
}
function dragEnd() {
    console.log('drag ended');
}

draggable elements with javascript

¡Excelente! Cuando se arrastra un elemento, los eventos se disparan. Ahora, en lugar de simplemente registrar el mensaje, apliquemos un nombre de clase a la tarjeta. Comencemos haciendo invisible la tarjeta movida para que desaparezca de la lista original. Aplicaremos estilo al elemento arrastrado y agregaremos lógica para que aparezca en una nueva lista un poco más tarde.

No es necesario hacer desaparecer el elemento; también puede hacer que se desvanezca ajustando la opacidad para ilustrar que se está arrastrando de un lugar a otro. ¡Sientete libre de ser creativo!

Modifiquemos la función dragStart():

1
2
3
4
function dragStart() {
    console.log('drag started');
    setTimeout(() => this.className = 'invisible', 0)
}

Ahora, no solo interactuamos con las cartas. También queremos interactuar con cada columna para aceptar una nueva tarjeta y eliminar tarjetas de columnas antiguas. Para esto, querremos ejecutar métodos cuando se disparen eventos en las columnas, ¡al igual que para los elementos!

Recorramos y agreguemos detectores de eventos a las columnas:

1
2
3
4
5
6
columns.forEach(column => {
    column.addEventListener('dragover', dragOver);
    column.addEventListener('dragenter', dragEnter);
    column.addEventListener('dragleave', dragLeave);
    column.addEventListener('drop', dragDrop);
});

Probemos los detectores de eventos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function dragOver() {
    console.log('drag over');
}
function dragEnter() {
    console.log('drag entered');
}
function dragLeave() {
    console.log('drag left');
}
function dragDrop() {
    console.log('drag dropped');
}

Cuando vea esto en su navegador, cuando arrastre elementos, deberían desaparecer y los registros de la consola deberían aparecer cuando “cruce” una nueva columna con el elemento:

dragging events in javascript

{.icon aria-hidden=“true”}

Nota: Si examina cuidadosamente el área de la consola, descubrirá que el método dragDrop() no registró ningún mensaje. Para que esto funcione, debe deshabilitar el comportamiento predeterminado en el método dragOver().

1
2
3
4
function dragOver(e) {
  e.preventDefault()
  console.log('drag over');
}

Podrás notar que "arrastrar y soltar" se registra ahora, cuando sueltas el elemento.

¡Estos son todos los eventos que necesitamos! Ahora, solo queremos implementar la lógica de eliminar elementos, agregarlos a nuevas columnas cuando se sueltan, etc. Como solo hay un elemento que arrastraremos en un momento dado, vamos a s crear una variable global para ello. Dado que cambiaremos la referencia comúnmente, será let, no const.

Cuando se inicia el arrastre, estableceremos este elemento en dragItem, agregándolo a la columna en la que estamos soltando, y lo estableceremos en null:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let dragItem = null;

function dragStart() {
    console.log('drag started');
    dragItem = this;
    setTimeout(() => this.className = 'invisible', 0)
}

function dragEnd() {
    console.log('drag ended');
    this.className = 'item'
    dragItem = null;
}

function dragDrop() {
    console.log('drag dropped');
    this.append(dragItem);
}

{.icon aria-hidden=“true”}

Nota: Se puede acceder a cada elemento que emite eventos a través de la palabra clave this, dentro del método llamado cuando se dispara el evento.

Eso es todo: ahora podemos arrastrar y soltar tarjetas de una columna a otra:

drag and drop in vanilla javascript

No necesitamos usar todos los eventos disponibles para que esto funcione; se agregan y se pueden usar para estilizar aún más el proceso.

Una cosa a tener en cuenta es que agregamos elementos secuencialmente al final de cada columna, siempre, ya que no hacemos un seguimiento de su posición relativa y simplemente llamamos append() cuando sea necesario. ¡Esto se puede arreglar fácilmente con la biblioteca Clasificable!

Implementación de arrastrar y soltar mediante SortableJS

Sortable es un módulo de JavaScript liviano y simple que utiliza la API nativa de arrastrar y soltar de HTML5 para ordenar una lista de objetos, ¡tal como lo hemos hecho nosotros! Es compatible con todos los navegadores contemporáneos y dispositivos táctiles.

Es excelente para ordenar elementos dentro de una lista y le permite arrastrar y soltar elementos dentro de una columna, en diferentes posiciones, en lugar de solo entre columnas. ¡Esta será una gran adición para nuestra aplicación!

De hecho, al usar Sortable, podemos automatizar completamente todo el proceso al tener un grupo de columnas, donde cada una puede compartir elementos.

Sortable se puede importar a través de un CDN:

1
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js" integrity="sha512-zYXldzJsDrNKV+odAwFYiDXV2Cy37cwizT+NkuiPGsa9X1dOz04eHvUWVuxaJ299GvcJT31ug2zO4itXBjFx4w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

O instalado a través de NPM:

1
$ npm install sortablejs --save

Usar Sortable es tan fácil como instanciar un objeto ‘Ortable’, en un elemento HTML dado:

1
2
3
4
5
6
const column = document.querySelector('.column');

new Sortable(column, {
    animation: 150,
    ghostClass: 'blue-background-class'
});

Hay una cantidad decente de propiedades que puede configurar para personalizar el proceso, dos de las cuales hemos usado. La animación es el tiempo de animación, expresado en milisegundos, mientras que la ghostClass se puede usar para estilizar cómo se ve el "fantasma" del elemento arrastrado. Esto hace que la experiencia de arrastrar un elemento sea mucho más agradable.

Volvamos a nuestro ejemplo de Trello y apliquemos Sortable a la tarea. Requiere que usemos la clase list-group-item en lugar de item:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="container">
    <div class="column">
        <h1>All Tasks</h1>
        <div class="list-group-item" draggable="true">Wash Clothes</div>
        <div class="list-group-item" draggable="true">Take a stroll outside</div>
        <div class="list-group-item" draggable="true">Design Thumbnail</div>
        <div class="list-group-item" draggable="true">Attend Meeting</div>
        <div class="list-group-item" draggable="true">Fix workshop</div>
        <div class="list-group-item" draggable="true">Visit the zoo</div>
    </div>
    <div class="column">
        <h1>In progress</h1>
    </div>
    <div class="column">
        <h1>Paused</h1>
    </div>
    <div class="column">
        <h1>Under Review</h1>
    </div>
    <div class="column">
        <h1>Completed</h1>
    </div>
</div>

Apliquemos el mismo estilo que antes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.container {
    font-family: "Trebuchet MS", sans-serif;
    display: flex;
    gap: 30px;
}
.column {
    flex-basis: 20%;
    background: #ddd;
    min-height: 90vh;
    padding: 5px;
    border-radius: 10px;
}
.column h1 {
    text-align: center;
    font-size: 22px;
}
.list-group-item {
    background: #fff;
    margin: 20px;
    padding: 20px;
    border-radius: 5px;
    cursor: pointer;
}

Ahora, vamos a crear una instancia de Ordenable para cada columna de la página, estableciendo su grupo en "compartido" para que las tarjetas se puedan compartir entre columnas:

1
2
3
4
5
6
7
8
9
const columns = document.querySelectorAll(".column");

columns.forEach((column) => {
    new Sortable(column, {
        group: "shared",
        animation: 150,
        ghostClass: "blue-background-class"
    });
});

¡Eso es todo! Sortable se encarga del resto:

drag and drop in javascript with sortableJS

Conclusión

En este artículo, hemos echado un vistazo a cómo arrastrar y soltar elementos en HTML5 y también cómo usar la biblioteca ordenable de JavaScript.