Trasteando con el DOM 6: drag and drop

En esta entrada veremos cómo funciona el drag and drop de js y html5

Tarsila do Amaral

archivado en: JavaScript / 11 octubre, 2014 / taller:

Sigo con la serie de javaScript para diseñadores. En esta entrada veremos algo divertido para compensar el tochako teórico de la anterior y es el drag and drop, es decir, el arrastrar cosas encima de cosas y lo haremos emulando el célebre experimento del píxel de Shröndinger.

El mecanismo básico es más sencillo que un botijo. En los elementos que se van a arrastrar añadimos el atributo draggable con valor true.

<div draggable="true">Soy un div <b>draggable</b></div>

Esos elementos se comportarán igual que otros que de forma natural se pueden arrastrar, como las imágenes, por lo que ya solo necesitamos vincularle escuchas al elemento para que sucedan cosas cuando sea dragueado. Los eventos disponibles son:

  • dragstart: cuando un elemento empieza a ser arrastrado.
  • drag: mientras es arrastrado.
  • dragend: cuando termina de ser arrastrado.
  • dragover: cuando un elemento arrastrado se sitúa sobre un elemento que puede recibirlo (droppeable). (Variantes: dragenter y dragleave).
  • drop: cuando un elemento recibe el final de un dragueado.

Todos estos eventos reciben por defecto un objeto, denominado e o event por convención, que tiene una montonera de propiedades relacionadas con el evento: en qué coordenadas comienza, sobre qué elemento, etcétera. De esa manera, podemos hacer cosas con elemento que se está dragueando, como bajarle la opacidad, que es un recurso habitual para que el usuario recuerde qué está arrastrando.

document.addEventListener("dragstart", function( event ) {

console.log('Coordenada x', event.offsetX);

console.log('Coordenada y', event.offsetY);

console.log('Elemento', event.target);

event.target.style.opacity = .5;

}, false);

La paradoja del píxel de Shrödinger

Como soltar toda la teoría de sopetón es un poco pesado, vamos a ir viendo todo el proceso completo con un ejercicio-demo. Como es sabido, la paradoja del píxel de Shrödinger sostiene que un píxel encerrado en una caja con una botella de gas venenoso no está ni vivo ni muerto hasta que es observado. Así que haremos un píxel gordote para que podamos arrastrarlo con comodidad, unas botellas de veneno y una caja donde las meteremos junto con el píxel.

Algo así:

<h3>Píxel</h3>

<div class="drag" draggable="true" id="pixel"></div>

<h3>Venenos</h3>

<div class="venenos" draggable="true" data-tipoVeneno="arsenico" id="veneno_1">A</div>

<div class="venenos" draggable="true" data-tipoVeneno="cicuta" id="veneno_2">C</div>

<div class="venenos" draggable="true" data-tipoVeneno="mandragora" id="veneno_3">M</div>

<h3>Caja</h3>

<div class="drop" id="caja"></div>

Que podemos acompañar con unas CSS parecidas a estas:

h3 {

margin-bottom: 0.2em;

}

.drag {

width: 25px;

height: 25px;

background: #900;

cursor: move;

}

.drop {

width: 100px;

height: 100px;

background: #ededed;

border: 1px solid #000;

}

.venenos {

width: 50px;

height: 50px;

background: #090;

display:inline-block;

margin-right: 10px;

cursor: move;

}

Ahora en una función que invocamos en un onload del body, ponemos la primera escucha, que será cuando comience el drag del píxel y luego otra para cuando termine:

function init() {

var pixel = document.getElementById('pixel');

pixel.addEventListener("dragstart", empiezaDrag);

pixel.addEventListener("dragend",finalizaDrag);

}

Con esto ya podemos hacer alguna cosa, como bajarle la opacidad al píxel cuando comience a ser arrastrado y volvérsela a subir cuando termine.

function empiezaDrag(event) {

event.target.style.opacity = .5;

}

function finalizaDrag(event) {

event.target.style.opacity = 1;

}

Vamos a hacer lo mismo con la caja, que cuando tenga encima un elemento arrastrado pierda opacidad y viceversa. Fijaos que event, al igual que sucedería con this, es lo que arrastramos en dragstart, pero en dragenter es lo que recibe lo que se está arrastrando.

var caja = document.getElementById('caja');

caja.addEventListener('dragenter', entraDrag);

caja.addEventListener('dragleave', saleDrag);
function saleDrag(event) {

/* Aquí this y event es el elemento que recibe el drag */

console.log(this);

event.target.style.opacity = 1;

}

function finalizaDrag(event) {

event.target.style.opacity = 1;

}

Ya hemos avanzado un buen trecho, pero aún nos falta algo muy importante y es prevenir el comportamiento por defecto en el dragover. En teoría, los navegadores piensan que solo se pueden arrastrar cosas sobre unos elementos en concreto: inputs, textareas y elementos con content-editable en true. Así que si tratamos de soltar nuestro píxel en la caja nos dirá que naranjas de la china.

Para solucionar esto, hay que decirle que en dragover, dragenter, dragleave y drop, el navegador no se comporte de la forma habitual, es decir, tenemos que aplicar un preventDefault en las dos funciones que ya tenemos y en una tercera para el dragover.

function permaneceDrag(event) {

event.preventDefault();

}

caja.addEventListener('dragover', permaneceDrag);

Ahora el problema es cómo saber qué ha sido dragueado en la función del drop, pues recordemos que ahí tanto event como this hacen referencia al elemento que recibe lo arrastrado. Es decir, ¿cómo podemos hacer algo tan sencillo como borrar el elemento arrastrado y dejarlo dónde ha sido arrastrado?

Hay al menos dos soluciones. Una es declarar una variable global a las funciones dragstart y drop y almacenar ahí el elemento que se está arrastrando cuando empieza el proceso.

var itemDragueado = null;

function empiezaDrag(event) {

/* Aquí this es el elemento que está siendo dragueado */

itemDragueado = this;

}

De esa forma, podemos recuperarlo desde el drop.

caja.addEventListener('drop', terminaDrop);

function terminaDrop(event) {

/* Detenemos el comportamiento por defecto para prevenir comportamientos inesperados */

event.preventDefault();

/* En el caso del píxel, vamos a jugar con almacenarlo en una variable global a esta función */

event.target.appendChild( itemDragueado );

event.target.style.opacity = 1;

}

La otra solución es recurrir al objeto dataTransfer.

dataTransfer

Este objeto, opcional, es inherente a toda operación drag and drop y tiene estas propiedades principales:

1) dropEffect

Esta propiedad se define en el dragenter y el dragover y sirve para indicar qué tipo de operación admite el elemento que recibe el drop (lo cual se refleja en la forma que adopta el cursor. Puede tener cuatro valores: copy (copiar), move (mover), link (enlazar) y none (ninguno).

function permaneceDrag(event) {

event.preventDefault();

event.dataTransfer.dropEffect = 'move';

}

2) effectAllowed

Esta propiedad se define en el dragstart y se combina con la anterior para modificar lo que se puede o no dropear. Puede tener estos valores:

  • all: cualquier acción,
  • copy: solo copiar,
  • copyLink: copiar y enlazar,
  • copyMove: copiar y mover,
  • link: enlazar,
  • linkMove: enlazar y mover,
  • move: mover,
  • none: ninguno.

Si no se define, el comportamiento por defecto será mover en los elementos editables, enlazar para las anclas y copiar para el resto de los elementos.

Por ejemplo, si definimos el dropEfect como move en el dragenter y en el dragover, podemos hacer algo así para que solo se pueda arrastrar la mandrágora, que es fatal para los píxeles.

function empiezaDragVenenos(event) {

itemDragueado = this;

/* Leemos el valor de data-tipoVeneno; solo vamos a dejar que se arrastre la mandrágora, al equiparar el effectAllowed con el dropEffect */

if ( event.target.dataset.tipoveneno == "arsenico" ) {

event.dataTransfer.effectAllowed = 'link';

} else if ( event.target.dataset.tipoveneno == "cicuta" ) {

event.dataTransfer.effectAllowed = 'copy';

} else if ( event.target.dataset.tipoveneno == "mandragora" ) {

event.dataTransfer.effectAllowed = 'move';

}

}

3) types y files

Estas dos propiedades, que no van en la tostadora el explorer, están relacionadas con el drag and drop de archivos externos, es decir, con los que arrastramos a un elemento de la página para subirlos, un tema que veremos en otra entrada más adelante.

Además, dataTransfer cuenta, entre otros, con los métodos setData(), getData() y clearData() que sirven para transmitir información durante el proceso de drag and drop. La información se agrupa por tipos MIME y se recupera leyendo cada tipo. Por ejemplo, si en setData(), que se pone en dragstart, indicamos que en "text/plain" irá "Soy una cadena de texto"...

event.dataTransfer.setData("text/plain", "Soy una cadena de texto");

...cuando luego leamos eso con getData(), por lo general en el drop, obtendremos "Soy una cadena de texto".

event.dataTransfer.getData("text/plain") // "Soy una cadena de texto"

Sin entrar en detalles con el drag de archivos, que veremos en otra entrada, los dos tipos fundamentales que manejaremos son text/plain, para cadenas normales, y text/html cuando hay tags.

Así, por ejemplo, podemos guardar el id del veneno y luego recuperarlo en el drop para poner a nuestro pixel en estado undefined para llevar a la práctica la paradoja de Shröndinger.

function empiezaDragVenenos(event) {

...

event.dataTransfer.setData("text/plain", event.target.id);

}

...

function terminaDrop(event) {

var veneno;

var pixel = null;

...

veneno = event.dataTransfer.getData("text/plain");

veneno = document.getElementById(veneno);

event.target.appendChild(veneno);

pixel = undefined;

...

}

ver demo en plunker

Chulo, ¿no?

|| Tags:

valoración de los lectores sobre Trasteando con el DOM 6: drag and drop

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 4.7 sobre 5 (3 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

Los comentarios están cerrados.