Arrastrar y Soltar en HTML5
Sin duda la posibilidad que introdujo HTML5 para Arrastrar y Soltar de manera nativa representó un avance, esto sin duda fue una novedad ya que en la era pre-HTML5, la única manera de lograr esto era con la utilización de complejas operaciones con JavaScript, por lo que la mayoría de los desarrolladores terminábamos utilizando alguna librería como JQuery, MooTools u otras para contar con la funcionalidad de Arrastrar y Soltar (Drag and Drop), o si la situación requería de mucha interactividad y gráficos la utilización de Flash era casi imperiosa. La clase nativa para Arrastrar y Soltar de HTML5 lleva un tiempo existiendo pero tenía varios problemas que se han ido solucionando poco a poco. El principal problema quizá era que los ejemplos que se podía encontrar por ahí, continuamente solo funcionaban en un navegador y no en los demás, pero los fabricantes han ido homologando cada vez más la clase aunque no todos todavía. La clase aún no es totalmente perfecta y tiene aún varios detractores y críticos muy enfáticos, sin embargo, posee varias ventajas sobre otras opciones. En esta entrada abordaré lo que necesita saber para comenzar a utilizar la clases Arrastrar y Soltar de HTML5 explicando sus puntos más básicos e importantes. Puede ver el ejemplo que a continuación explicaré en este enlace, comencemos…
Ventajas sobre otras implementaciones Arrastrar y Soltar
Como mencioné, he podido encontrar que en varios blogs aseguran que el Arrastrar y Soltar de HTML5 no debería ser utilizado, a lo cual me gustaría contra argumentar, sobre todo porque la clase me ha sido de gran ayuda últimamente:
- Interacción con otras aplicaciones o aplicacion de derivadas. La implementación del Arrastrar y Soltar de HTML5 permite la interacción entre iframes e incluso entre ventanas de navegadores. Si me permiten ser un poco entusiasta en este punto, invito a quien sea a que intente implementar un Arrastrar y Soltar entre iframes y/o ventanas utilizando librerías JavaScript…¡el horror!….
- El framework nativo es independiente de terceros. Tener una clase nativa siempre tiene la ventaja de independencia en cuanto a las situaciones que pueden rodear y afectar al desarrollo de un tercero, lo que evidentemente no sucede con las alternativas que presentan JQuery, MooTools, Dojo, etc., ahí la dependencia es total.
- Interacción con aplicaciones que no son web. Un ejemplo de esto es que ahora es posible arrastrar un archivo del escritorio a una aplicación en el navegador.
Las bases, paso a paso
Para facilitar un poco las cosas he organizado esta entrada en pasos, cada paso describe un concepto o varios que iré ejemplificando.
1. Especificando los elementos arrastrables (draggable object)
Lo primero que necesita hacer es definir que nodo o nodos del documento HTML5 serán susceptibles de ser arrastrados, en HTML5 casi cualquier elemento visible puede ser arrastrado, como son imágenes, divs, links, etc., para lograr esto se utiliza el atributo draggable=”true”. Por ahora no aplica el manejo de valores falso o verdadero a la manera clásica de XHTML en los navegadores que he probado, es decir, al atributo no funciona si lo escribimos como draggable=”draggable” incluso dentro de un archivo xhtml. También hay elementos que son arrastrables por default para algunos navegadores, como lo son imágenes y selecciones de texto. En el sigiente ejemplo se muestran algunos cuadros que serán los elementos arrastrables para este ejemplo:
<div id="squares"> <div class="square" draggable="true"> <h1>1</h1> </div> <div class="square" draggable="true"> <h1>2</h1> </div> <div class="square" draggable="true"> <h1>3</h1> </div> </div>
Acompañando este código colocamos los siguientes estilos CSS:
.square { height: 100px; width: 100px; border: 1px solid #000; float: left; background-color: #eee; margin: 5px; -webkit-box-shadow: inset -1px -1px 3px #000; -ms-box-shadow: inset -1px -1px 3px #000; box-shadow: inset -1px -1px 3px #000; text-align: center; cursor: move; } h1 { font-size: 75px; } .target { height: auto; min-height: 150px; width: 320px; float: left; border: 1px solid #000; margin: 5px; background-color: #ccc; -webkit-box-shadow: inset -1px -1px 3px #000; -ms-box-shadow: inset -1px -1px 3px #000; box-shadow: inset -1px -1px 3px #000; text-align: center; cursor: move; }
En navegadores webkit (Chrome, Safari) y Firefox el código anterior nos presenta una serie de cuadrados que originarán una especie de “fantasma” si intenta arrastrarlos, ahora hay que agregar los eventos apropiados. Resultado:
2. Asignando eventos a los elementos arrastrables (Drag Objects)
Ahora que tenemos los objetos que deseamos se puedan arrastrar, debemos asignarles algunos eventos utilizando JavaScript, estos objetos pueden disparar lo siguientes tres eventos:
- dragstart. Se dispara cuando el usuario comienza a arrastrar el objeto.
- drag. Se dispara cada vez que el puntero del mouse se mueve mientras que un objeto está siendo arrastrado.
- dragend. Se dispara cuando el usuario suelta el botón mientras un objetos está siendo arrastrado. Estos eventos se asignan como lo hace normalmente JavaScript o la librería de su preferencia. Vamos a agregar unos cuantos elementos al código HTML:
<div id="squares"> <div class="square" draggable="true"> <h1>1</h1> </div> <div class="square" draggable="true"> <h1>2</h1> </div> <div class="square" draggable="true"> <h1>3</h1> </div> </div> <p id="event"></p> <p id="status_drag"></p> <p id="status_over"></p> <p id="status_drop"></p>
Agreguemos el siguiente código JavaScript para asignar los eventos descritos a nuestros cuadros:
/* * En el evento dragstart se coloca un mensaje que se verá muy brevemente * o quizá no se note, cuando el usuario inicia el arrastre */ function dragStartEvent(e) { eventStatus.innerHTML = "evento dragStart" } /* * El evento drag reporta al usario cuando inicia y que un arrastre * está actualmente en progreso */ function dragEvent(e) { eventStatus.innerHTML = "evento drag." dragStatus.innerHTML = "Arrastrando en este momento."; } /* * El evento dragover despliega un mensaje cuando es llamado y otro que * que indica que el arrastre ha terminado */ function dragEndEvent(e) { eventStatus.innerHTML = "evento dragend." dragStatus.innerHTML = "Arrastre terminado." } //variables para elementos informativos var eventStatus = document.getElementById('event'); var dragStatus = document.getElementById('status_drag'); //variable para alamacenar todos los divs que usan la clase square var squareItems = document.querySelectorAll('.square'); //ciclo para asignar los eventos a cada cuadro (div square) [].forEach.call(squareItems , function(squareItem) { squareItem.addEventListener('dragstart',dragStartEvent, false); squareItem.addEventListener('drag',dragEvent, false); squareItem.addEventListener('dragend',dragEndEvent, false); });
Este código deber ser llamado cuando el documento HTML está cargado, note como simplemente se asignaron los eventos y estos despliegan mensajes cuando son disparados.
3. Asignando eventos al elemento objetivo (Drop Object)
Hasta ahora hemos logrado arrastrar elementos por la ventana, pero tenemos que asignar algún objeto en donde los elementos arrastrables puedan ser soltados, a este elemento lo llamaremos objetivo o drop target. Al elemento objetivo o drop target le podemos asignar los siguientes eventos:
- dragenter. Se dispara cuando un objeto arrastrable es el primero en arrastrase dentro de un objeto.
- dragover. Se dispara cuando un objeto arrastrable es arrastrado dentro de un objeto. Hay que tener presente que si se desea que el objeto que está siendo arrastrado pueda ser soltado dentro de otro, debe cancelar el comportamiento por default de este.
- dragleave. Se dispara cuando un objeto arrastrable es arrastrado fuera de un nodo objetivo.
- drop. Se dispara cuando un objeto arrastrable es soltado dentro de un nodo objetivo. Si desea convertir un objeto en un elemento o nodo objetivo que permita soltar elementos arrastrables, debe de asignar los eventos dragover y drop de manera obligatoria, es fácil entender porque el evento drop debe ser usado, pero quizá no sea tan claro porque dragover también, la razón es porque el elemento que se está arrastrando puede tener otro funcionamiento que no se desea cuando se suelta, por ejemplo, si está arrastrando un enlace y no previene el comportamiento por default, cuando suelte este elemento el navegador se direccionará a la URL del enlace como si usted hubiera hecho un clic normal, lo que no es deseable mientras se arrastra y se suelta el elemento. Al código que tiene actualmente, agregue al final el elemento objetivos para soltar el elemento:
<div id="target1" class="target">Suelte elementos aquí</div>
Agregaremos dos variables solo para alertar cuando se disparan los nuevos eventos y tener mejor idea de que el elemento objetivo está siendo efectivamente afectado.
var overStatus = document.getElementById('status_over'); var dropStatus = document.getElementById('status_drop');
Y claro el código para asignar los nuevos elementos:
dropItem.addEventListener('dragover', dragOverEvent, false); dropItem.addEventListener('drop', dropEvent, false);
Los otros ventos que no incluí no son indispensables (dragenter y dragleave) pero son útiles, por ejemplo, para ofrecer más indicadores visuales al usuario y hacer una experiecia más intuitiva, pero por ahora lo omito para mayor claridad, si desea más ejemplos incluyendo estos eventos, recuerde que en el pedir está el dar 😉 Resultado:
4. Transferecia de datos del elemento arrastrable al elemento objetivo (Datratrasfer object).
En este punto está casi todo listo, pero donde culmina todo lo que hemos hecho es en la transferencia de datos del elemento que estamos arrastrando. Por ahora, aunque nuestro elemento parace estar haciendo todo bien, aún no lo logra ser “soltado” dentro del elemento objetivo como esperaríamos, es aquí donde la transferencia de datos hace su magia. Lo que se necesita es la propiedad dataTransfer y sus métodos setData() y getData(). Lo primero es utilizar el método setData() dentro de uno de los eventos asignados al elemento que se arrastra, usualmente se utiliza el evento dragstart, por parte de los eventos del elemento objetivo, estos pueden recibir los datos transferidos en algunos de sus eventos asignados, es ahí de donde se utiliza getData(), entonces: setData(format, data). Establece la información que se intercambia entre el nodo que se arrastra y y el nodo objetivo. El parámetro format establece el tipo de dato que se va a intercambiar, hasta ahorita los únicos tipos de dato que existan son “text” y “url” (se usa el formato de mimetype). El parámetro data es el información persé que sera intercambiada, lo cual es una cadena de texto, comunmente se utiliza innerHTML. getData(format). Recupera la información que fue previamente asignada por setData(), el setData puede estar incluso en otra página, incluso en otro navegador. El parámetro format es el tipo de dato asociado con lo que se espera, los tipos de dato pueden ser “text” y “url” (se usa el formato de mimetype). Ahora utilicemos lo que hemos aprendido en nuestro código. Dentro del evento dragstart coloquemos la siguiente línea:
e.dataTransfer.setData('text/html', this.innerHTML);
Con la línea anterior ha preparado la información que se va a transferir en el arrastre, ahora toca el turno de indicarle al nodo objetivo cual es información que va a recibir cuando el usuario suelte el elemento que está arrastrando, sin más agreguemos las siguientes líneas de código al evento drop:
var dropedelement = document.createElement('span'); dropedelement.innerHTML = e.dataTransfer.getData('text/html'); this.appendChild(dropedelement);
Para terminar.
El código JavaScript completo debería lucir aproximadamente de la siguiente manera:
/* * En el evento dragstart se coloca un mensaje que se verá muy brevemente * o quizá no se note, cuando el usuario inicia el arrastre */ function dragStartEvent(e) { eventStatus.innerHTML = "evento dragStart."; overStatus.innerHTML = ""; dropStatus.innerHTML = ""; e.dataTransfer.setData('text/html', this.innerHTML); } /* * El evento drag reporta al usario cuando inicia y que un arrastre * está actualmente en progreso */ function dragEvent(e) { eventStatus.innerHTML = "evento drag."; dragStatus.innerHTML = "Arrastrando en este momento."; } /* * El evento dragover despliega un mensaje cuando es llamado y otro que * que indica que el arrastre ha terminado */ function dragEndEvent(e) { eventStatus.innerHTML = "evento dragend."; dragStatus.innerHTML = "Arrastre terminado."; } /* * El evento dragend despliega un mensaje cuando es llamado y otro que * que indica sobre que elemento objetivo se arratra actualemte, si es * el caso */ function dragOverEvent(e) { if (e.preventDefault) { e.preventDefault(); // Necesario para permitir soltar. } eventStatus.innerHTML = "evento over."; overStatus.innerHTML = "Elemento arrastrable sobre " + this.id; } /* * El evento drop despliega un mensaje cuando es llamado y otro que * que indica en que elemento objetivo se ha soltado el elemento */ function dropEvent(e) { eventStatus.innerHTML = "evento drop"; dropStatus.innerHTML = "Se ha soltado un elemento dentro de " + this.id; var dropedelement = document.createElement('span'); dropedelement.innerHTML = e.dataTransfer.getData('text/html'); this.appendChild(dropedelement); } //variables para elementos informativos var eventStatus = document.getElementById('event'); var dragStatus = document.getElementById('status_drag'); //variables para elementos informativos var overStatus = document.getElementById('status_over'); var dropStatus = document.getElementById('status_drop') //variable para alamacenar todos los divs que usan la clase square var squareItems = document.querySelectorAll('.square'); var dropItem = document.getElementById('target1'); //ciclo para asignar los eventos a cada cuador (div square) [].forEach.call(squareItems , function(squareItem) { squareItem.addEventListener('dragstart',dragStartEvent, false); squareItem.addEventListener('drag',dragEvent, false); squareItem.addEventListener('dragend',dragEndEvent, false); }); dropItem.addEventListener('dragover', dragOverEvent, false); dropItem.addEventListener('drop', dropEvent, false);
Lo que obtenemos tiene este aspecto después de arrastrar todos los elementos:
Note que este ejemplo copia los elementos en lugar de moverlos (no elimina el elemento original) pero usted puede hacerlo si lo desea. Por supuesto se pueden hacer implementaciones un tanto más sofisticadas, pero por ahora usted ha cubierto las bases para posteriormente hacer un uso más interesante e intenso de la la clase Arrastrar y Soltar de HTML5. Si usted tiene alguna opinión, crítica, aportación o cualquier otra cosa, deje un comentario en esta entrada, o bien envíelo a @ManuGekko o @RealInernet80 en Twitter.
Otros enlaces de interés:
Arrancar con HTML5 Curso de Programación
Transiciones y Transformaciones en Botones (CSS3 y HTML5)
Emmanuel Herrera
IT professional with several years of experience in management and systems development with different goals within public and private sectors.
Emmanuel worked through development and management layers, transitioning from developer and team development leader to Project Manager, Project Coordinator, and eventually to Scrum Master, Product Owner, and Agile Coach.
Some certifications include: PSM, PSPO, SSM.
Excelente articulo. Muchísima gracias, creo que me puede servir para mi proyecto web. Lo que pasa es que tengo que poder arrastrar y soltar una pequeña imagen sobre el punto concreto o coordenada de un elemento target que es un dibujo de un continente o mapa, y aunque el Target podrá ser todo el recuadro de la imagen (ya que también se podrá soltar sobre el mar), necesitaría saber si al soltarlo quedará justo en el “SetFocus()”, es decir, donde este el cursor en la hora de soltarlo. Después al clickar esa pequeña imagen debe llevarle a una ventana flotante editable (Todo ello en una sesión de usuario y con conexión a BD de MySQL).
Te estaría muy agradecido si me pudieras orientar o si conoces algún articulo con algunos ejemplos que me puedan servir. Muchísimas gracias. Excelente trabajo.
Un saludo, (Albert)