Esta es una de las nuevas características añadidas en HTML5, aunque ya se podía hacer drag and drop mediante otras técnicas, ahora al estar incluido en HTML5 hace que «supuestamente» funcione en todos los navegadores aunque si no cumplen los estándares como es el caso de Internet Explorer te encontraras en que en principio no funciona.
Para explicar estas nuevas funcionalidades me voy a servir de 2 ejemplos sencillos. En el primero veremos como arrastrar y soltar elementos, clonarlos o eliminarlos, y en el segundo un sencillo puzzle de 4 piezas.
Para que se pueda arrastrar y soltar un elemento hacen falta dos tipos de elementos, un elemento arrastrable (que se consigue añadiéndole al menos los atributos draggable=»true» y ondragstart=»la_funcion_que_sea(event)») y un elemento en el que se pueda soltar a el que como mi mínimo hay que poner ondragover=»return la_funcion_que_sea(event)» y ondrop=»return la_funcion_que_sea(event)».
Ejemplo 1 de arrastrar y soltar con HTML5: Arrastrar y soltar, clonar y eliminar elementos
En el siguiente ejemplo los elementos arrastrable1,2,3 y 4 son arrastrables y en los elementos cuadro1,2,3 y papelera se pueden soltar elementos.
<!DOCTYPE html>
<html land="es">
<head>
<meta charset="utf-8" />
<title>Drag & drop</title>
<link rel="stylesheet" type="text/css" href="estilo.css">
<script type="text/javascript">
//...
</script>
</head>
<body>
<header>
<h1> Un ejemplo del uso de Drag & Drop en HTML5 </h1>
</header>
<section>
<div id="cuadro1" ondragenter="return enter(event)" ondragover="return over(event)" ondragleave="return leave(event)" ondrop="return drop(event)">
<div class="cuadradito" id="arrastrable1" draggable="true" ondragstart="start(event)" ondragend="end(event)">
1
</div>
<div class="cuadradito" id="arrastrable2" draggable="true" ondragstart="start(event)" ondragend="end(event)">
2
</div>
<div class="cuadradito" id="arrastrable3" draggable="true" ondragstart="start(event)" ondragend="end(event)">
3
</div>
</div>
<div id="cuadro2" ondragenter="return enter(event)" ondragover="return over(event)" ondragleave="return leave(event)" ondrop="return drop(event)">
</div>
<div id="cuadro3" ondragenter="return enter(event)" ondragover="return over(event)" ondragleave="return leave(event)" ondrop="return clonar(event)">
Clonadora
</div>
<div id="papelera" ondragenter="return enter(event)" ondragover="return over(event)" ondragleave="return leave(event)" ondrop="return eliminar(event)">Papelera</div>
</section>
</body>
</html>
Vamos a explicar brevemente para que es cada atributo del elemento arrastrable:
- draggable: Si es true el elemento se puede arrastrar, si es false o se omite no se puede mover el elemento.
- ondragstart: Aquí se indicara la acción que se llevara acabo cuando se empiece a arrastar el elemento.
- ondragend: Aquí se indicara la acción que se llevara acabo cuando se termine de arrastar el elemento (cuando se suelte).
Y para el elemento al que se puedan arrastrar son estos:
- ondragenter: Aquí se indicara la acción que se llevara acabo cuando un elemento arrastrable entre dentro del elemento.
- ondragover: Aquí se indicara la acción que se llevara acabo cuando un elemento arrastrable este sobre el elemento. En esta función es donde se indica que elementos arrastrables se pueden soltar aquí.
- ondragleave: Aquí se indicara la acción que se llevara acabo cuando un elemento arrastrable deje de estar encima del elemento.
- ondrop: Aquí se indicara la acción que se llevara acabo cuando se suelte un elemento arrastrable sobre el elemento.
Una vez visto cual es el código HTML necesario para poder arrastrar y soltar elementos vamos a ver la parte de javaScript. Empezamos por las funciones del elemento arrastrable.
La primera acción que ocurre es la desencadenada dentro de ondragstart:
function start(e) {
e.dataTransfer.effecAllowed = 'move'; // Define el efecto como mover
e.dataTransfer.setData("Data", e.target.id); // Coje el elemento que se va a mover
e.dataTransfer.setDragImage(e.target, 0, 0); // Define la imagen que se vera al ser arrastrado el elemento y por donde se coje el elemento que se va a mover (el raton aparece en la esquina sup_izq con 0,0)
e.target.style.opacity = '0.4'; // Establece la opacidad del elemento que se va arrastrar
}
En esta función se define el modo en el que se va a arrastrar el elemento (none, copy, copyLink, copyMove, link, linkMove, move o all) en effecAllowed, se establece el elemento que se va a arrastrar y el modo en el que se va a guardar dicha información con setData(formato, elementó) y también se puede sustituir la imagen del elemento cuando se esta arrastrando para sustituir la que el navegador usa por defecto (el elemento con cierto grado de transparencia) por la imagen (o el elemento) indicada y las coordenadas del elemento por las que se arrastrara el elemento. De estas tres funciones solo setData es obligatoria. Además de esas funciones en la función anterior también se modifica la opacidad del elemento que se arrastra para que se vea un poco transparente y se vea mejor el elemento que se esta arrastrando.
Cuando se termina de arrastrar un elemento se elimina la información asociada al elemento dataTransfer mediante setData con el método clearData y como en al comenzar a arrastrar el elemento se cambio la opacidad del elemento se restaura a su estado original.
function end(e) {
e.target.style.opacity = ''; // Restaura la opacidad del elemento
e.dataTransfer.clearData("Data");
}
Ahora vamos a ver las funciones que se ejecutan cuando se producen los eventos del elemento contenedor:
Las funciones que se ejecutan en los eventos ondragenter y ondragleave se pueden usar por ejemplo para cambiar el aspecto cuando entra o sale un elemento arrastrable del elemento desde el que se llaman, en este ejemplo se pone un borde punteado gris cuando un elemento arrastrable pasa por encima y se quita el borde cuando se sale.
function enter(e) {
e.target.style.border = '3px dotted #555';
}
function leave(e) {
e.target.style.border = '';
}
Cuando se coloca un elemento arrastrable sobre un elemento en el que se ‘pueda’ soltar se ejecuta el evento ondragover desde el que se llama a la siguiente función:
function over(e) {
var elemArrastrable = e.dataTransfer.getData("Data"); // Elemento arrastrado
var id = e.target.id; // Elemento sobre el que se arrastra
// return false para que se pueda soltar
if (id == 'cuadro1') {
return false; // Cualquier elemento se puede soltar sobre el div destino 1
}
if ((id == 'cuadro2') && (elemArrastrable != 'arrastrable3')) {
return false; // En el cuadro2 se puede soltar cualquier elemento menos el elemento con id=arrastrable3
}
if (id == 'cuadro3') {
return false;
}
if (id == 'papelera') {
return false; // Cualquier elemento se puede soltar en la papelera
}
}
Lo mas importante de esta función es lo que devuelve, si se puede soltar el elemento devuelve false (si, no es un error, devuelve false si se puede soltar) y true en caso contrario.
Para indicar que elementos se pueden soltar y donde se pueden soltar en la línea 2 se obtiene el elemento que se esta arrastrando y en la 3 el id del elemento sobre el que se esta arrastrando y combinando ambos se puede conseguir que no todos los elementos se puedan soltar en todos los «contenedores».
La siguiente función permite que se sitúe el elemento en la posición exacta en la que se suelta. Con las líneas 2 y 3 el elemento arrastrado ya estaría dentro del elemento «contenedor», pero para que se coloque en la posición exacta que queramos hay que usar posicionamiento absoluto, por lo que tenemos que sacar las coordenadas en las que se ha soltado el elemento (líneas 14 y 15), pero si se suelta en un borde del «contendor» puede ser que parte del elemento arrastrado quede fuera del contenedor, lo que no resulta muy estético por lo que son necesarias las coordenadas del contenedor y el tamaño del contenedor y del elemento arrastrado para saber si el elemento queda fuera o dentro y si queda fuera corregir la posición para que quede dentro. (Solo esta comprobado que no se salga por la derecha y por abajo porque como en setDragImage las coordenadas eran 0,0 no se puede salir por arriba ni por la izquierda.) En la ultima línea de la función se restaura el borde al estado original que se había cambiado en la función enter.
function drop(e) {
var elementoArrastrado = e.dataTransfer.getData("Data"); // Elemento arrastrado
e.target.appendChild(document.getElementById(elementoArrastrado)); // Añade el elemento arrastrado al elemento desde el que se llama a esta funcion
// Dimensiones del elemento sobre el que se arrastra
tamContX = $('#'+e.target.id).width();
tamContY = $('#'+e.target.id).height();
// Dimensiones del elemento arrastrado
tamElemX = $('#'+elementoArrastrado).width();
tamElemY = $('#'+elementoArrastrado).height();
// Posicion del elemento sobre el que se arrastra
posXCont = $(e.target).position().left;
posYCont = $(e.target).position().top;
// Posicion absoluta del raton
x = e.layerX;
y = e.layerY;
// Si parte del elemento que se quiere mover se queda fuera se cambia las coordenadas para que no sea asi
if (posXCont + tamContX <= x + tamElemX){
x = posXCont + tamContX - tamElemX;
}
if (posYCont + tamContY <= y + tamElemY){
y = posYCont + tamContY - tamElemY;
}
document.getElementById(elementoArrastrado).style.position="absolute";
document.getElementById(elementoArrastrado).style.left=x+"px";
document.getElementById(elementoArrastrado).style.top=y+"px";
e.target.style.border = ''; // Quita el borde del cuadro al que se mueve
}
Con arrastrar y soltar también se pueden clonar elementos de una forma muy sencilla. En la primera línea se obtiene el elemento arrastrado para poder copiarlo y en la siguiente se restaura la opacidad para que el elemento clonado tenga la opacidad original. Después se clona el elemento y se le cambia el id al elemento clonado para que sea único con un nombre y un contador que será una variable global (Una variable fuera de las funciones) para que no se repita y se añade al elemento desde el que se llama a esta función (en el caso de este ejemplo cuadro3).
function clonar(e) {
var elementoArrastrado = document.getElementById(e.dataTransfer.getData("Data")); // Elemento arrastrado
elementoArrastrado.style.opacity = ''; // Dejamos la opacidad a su estado anterior para copiar el elemento igual que era antes
var movecarclone = elementoArrastrado.cloneNode(true); // Se clona el elemento
movecarclone.id = "ElemClonado" + contador; // Se cambia el id porque tiene que ser unico
contador += 1;
elementoClonado.style.position = "static"; // Se posiciona de forma "normal" (Sino habria que cambiar las coordenadas de la posición)
e.target.appendChild(movecarclone); // Se añade el elemento clonado
e.target.style.border = ''; // Quita el borde del "cuadro clonador"
}
Y para terminar este ejemplo, vamos con el código de la función para eliminar elementos arrastrables. Esta función es muy simple, lo único que hay que tener en cuenta es que para eliminar un elemento hay que hacerlo desde su elemento padre pero gracias al uso de parentNode solo se necesita el elemento que se quiere eliminar.
function eliminar(e) {
var elementoArrastrado = document.getElementById(e.dataTransfer.getData("Data")); // Elemento arrastrado
elementoArrastrado.parentNode.removeChild(elementoArrastrado); // Elimina el elemento
e.target.style.border = ''; // Quita el borde
}
Aquí puedes ver en funcionamiento el ejemplo completo:
See the Pen Ejemplo Drag & Drop en HTML5 by Ivan Salas (@isc7) on CodePen
Ejemplo 2 de arrastrar y soltar con HTML5: Un puzzle
Este ejemplo es bastante simple y sobretodo si previamente has visto el ejemplo anterior en el que se explican los diferentes aspectos del drag&drop con HTML5.
El código es muy similar al del ejemplo anterior, el HTML es análogo aunque puesto que en esta ocasión el objetivo es hacer un puzzle hay un contenedor inicial en el que están las piezas del puzzle y otro contenedor en el que se hará el puzzle que tiene otros cuatro contendores uno para cada pieza.
<div id="contenedorPiezas" ondragenter="return enter(event)" ondragover="return over(event)" ondrop="return drop(event)">
<img id="pieza3" src="pieza3.jpg" draggable="true" ondragstart="start(event)" ondragend="end(event)">
<img id="pieza2" src="pieza2.jpg" draggable="true" ondragstart="start(event)" ondragend="end(event)">
<img id="pieza4" src="pieza4.jpg" draggable="true" ondragstart="start(event)" ondragend="end(event)">
<img id="pieza1" src="pieza1.jpg" draggable="true" ondragstart="start(event)" ondragend="end(event)">
</div>
<div id="puzzle">
<div class="contenedorPieza" id="uno" ondragenter="return enter(event)" ondragover="return over(event)" ondrop="return drop(event)"></div>
<div class="contenedorPieza" id="dos" ondragenter="return enter(event)" ondragover="return over(event)" ondrop="return drop(event)"></div>
<div class="contenedorPieza" id="tres" ondragenter="return enter(event)" ondragover="return over(event)" ondrop="return drop(event)"></div>
<div class="contenedorPieza" id="cuatro" ondragenter="return enter(event)" ondragover="return over(event)" ondrop="return drop(event)"></div>
</div>
Y en cuanto a las funciones javaScript tampoco hay demasiado que comentar, únicamente en la función drop la primera línea tiene que ser e.preventDefault(); para evitar que al soltar las piezas que son imágenes se produzca la acción por defecto del navegador (que se habrá la imagen) y así funcione todo como es de esperar.
/**
* Función que se ejecuta al arrastrar el elemento.
*/
function start(e) {
e.dataTransfer.effecAllowed = 'move'; // Define el efecto como mover (Es el por defecto)
e.dataTransfer.setData("Text", e.target.id); // Coje el elemento que se va a mover
e.target.style.opacity = '0.4';
}
/**
* Función que se ejecuta se termina de arrastrar el elemento.
*/
function end(e){
e.target.style.opacity = ''; // Restaura la opacidad del elemento
e.dataTransfer.clearData("Data");
}
/**
* Función que se ejecuta cuando un elemento arrastrable entra en el elemento desde del que se llama.
*/
function enter(e) {
return true;
}
/**
* Función que se ejecuta cuando un elemento arrastrable esta sobre el elemento desde del que se llama.
* Devuelve false si el objeto se puede soltar en ese elemento y true en caso contrario.
*/
function over(e) {
if ((e.target.className == "contenedorPieza") || (e.target.id == "contenedorPiezas")) {
return false;
} else {
return true;
}
}
/**
* Función que se ejecuta cuando un elemento arrastrable se suelta sobre el elemento desde del que se llama.
*/
function drop(e) {
e.preventDefault(); // Evita que se ejecute la accion por defecto del elemento soltado.
var elementoArrastrado = e.dataTransfer.getData("Text");
e.target.appendChild(document.getElementById(elementoArrastrado)); // Coloca el elemento soltado sobre el elemento desde el que se llamo esta funcion
comprobarPuzzle();
}
function comprobarPuzzle(){
if ((document.getElementById('pieza1').parentNode.id=='uno') &&
(document.getElementById('pieza2').parentNode.id=='dos') &&
(document.getElementById('pieza3').parentNode.id=='tres') &&
(document.getElementById('pieza4').parentNode.id=='cuatro'))
{
alert('Felicidades, has hecho un puzzle de 4 piezas xD');
}
}
Para finalizar comentar la función comprobarPuzzle que simplemente comprueba si cada pieza esta en su posición y si es así lo indica con un mensaje. Esta función hay que llamarla cada vez que se coloca una pieza por lo que se llama desde la función drop.
Y finalmente el puzzle para que lo puedas ver:
See the Pen Puzzle Drag & Drop HTML5 by Ivan Salas (@isc7) on CodePen
Aquí puedes descargar el código de los dos ejemplos.
Muy bueno, nos puedes pasar los códigos por favor, muchísimas gracias.
He puesto el enlace para descargar el código al final del post, pero de todos modos creo que esta todo el código escrito en el post.
Y ya que he revisado el post he corregido el primer ejemplo que no se veia correctamente porque se me habia olvidado importar jQuery y por alguna cosa rara que no toma bien la posición del ratón en el post, si ha alguien le pasa algo similar le digo como lo he solucionado para que se vea bien en el blog.
Muchas gracias, me sirvió mucho para un proyecto que estoy haciendo… Saludos.
Muchísimas gracias.
Me funciona si lo ejecuto en Chrome desde el c drive, pero si lo ejecuto desde el localhost no
http://localhost/DragAndDropPuzle/draganddrop.html
El problema es que no reconoce la variable var elemArrastrable = e.dataTransfer.getData(«Data»);
Cómo podría solucionarlo?
Muchas gracias otra vez
Lo acabo de comprobar y tienes razón en Chrome no funciona bien lo de evitar que se pueda poner el arrastrable3 en el cuadro2 porque por motivos de «seguridad» (que no se pueda saber lo que estas arrastrando) la función dataTransfer.getData() no se puede usar en el evento ondragover pero si que se puede usar en el ondrop por lo que si metes el contenido de la función drop dentro de un if como en la función over se consigue el mismo efecto, con hacer esto ya funciona:
function drop(e){
var elementoArrastrado = e.dataTransfer.getData(«Data»); // Elemento arrastrado
if ((e.target.id == ‘cuadro2’) && (elementoArrastrado != ‘arrastrable3’)){
e.target.appendChild(document.getElementById(elementoArrastrado));
e.target.style.border = »; // Quita el borde
tamContX = $(‘#’+e.target.id).width();
tamContY = $(‘#’+e.target.id).height();
tamElemX = $(‘#’+elementoArrastrado).width();
tamElemY = $(‘#’+elementoArrastrado).height();
posXCont = $(‘#’+e.target.id).position().left;
posYCont = $(‘#’+e.target.id).position().top;
// Posicion absoluta del raton
x = e.layerX;
y = e.layerY;
// Si parte del elemento que se quiere mover se queda fuera se cambia las coordenadas para que no sea asi
if (posXCont + tamContX <= x + tamElemX){
x = posXCont + tamContX – tamElemX;
}
if (posYCont + tamContY <= y + tamElemY){
y = posYCont + tamContY – tamElemY;
}
document.getElementById(elementoArrastrado).style.position = «absolute»;
document.getElementById(elementoArrastrado).style.left = x + «px»;
document.getElementById(elementoArrastrado).style.top = y + «px»;
}
}
Hola he intentado probar con tu ejemplo, pero tengo un problema con la posición. En este caso al parecer no toma la posición y los elementos que arrastro los pone de forma descendente, me podrías ayudar o explicar como puedo solucionar este problema gracias
Muchas gracias, intento probarlo mañana y te comento
Hola!!!
Muchas gracias por el cógido. Funciona bien en firefox y en chrome, pero en Internet Explore no funciona. ¿Alguna solución?. Muchísimas gracias!!
Muchisisimas gracias¡¡
Te iras al cielo (espero no sea pronto XD)
había estado buscando una forma de validar y no encontraba nada¡¡
muchas gracias¡¡¡
hola estoy intentando probar con tu ejemplo y en la posicion hay algo q no permite colocar el cuadro pequeño en la posicion que se desea. si pudieras auxiliarme en eso te lo agradeseria muchisimo
Buen post
Excelente trabajo!!
en el caso de los cuadraditos, como puedo imprimir el resultado de las manipulaciones ?
Muchísmas gracias!
Pues la verdad es me parece que no hay que hacer nada especial, al fin y al cabo no deja de ser html normal y corriente, quizas definir unos css para imprimir para que se adapte al tamaño de una hoja porque puede que sean muy pequeño o que se salga.
Gracias Iván por tu respuesta.
Me explico mejor.
Si sobre esta página usas vista preliminar o imprimir y no tienes activada la opción de «imprimir fondo, colores e imágenes»…
observa que el rectangulo de papelera y clonadora salen sin sus colores rosa y azul.
He probado a poner «img» en los rectangulos sin usar «background», pero entonces no se depositan los «cuadraditos».
gracias otra vez
Hola. muchas gracias por el aporte, está de lujo!!
Una pregunta, en el ejemplo del puzzle, hay una forma de hacer que si no es el elemnto que va en ese espacio el div contenedor no lo acepte y regrese a su posición original?
De antemano muchas gracias, saludos
Si, siguiendo el mismo procedimiento que en el otro ejemplo en el que un cuadro no se puede soltar sobre uno de los contenedorres podriamos cambiar la funcion over para eso
function over(e) {
var elemArrastrable = e.dataTransfer.getData(«Text»); // Elemento arrastrado
var id = e.target.id; // Elemento sobre el que se arrastra
if (e.target.id == «contenedorPiezas») {
// Si se pueden quitar las pieza del puzzle
return false;
} else if (e.target.className == «contenedorPieza») {
// Solo se puede soltar cada pieza en su posicion correcta
if ((id == ‘uno’) && (elemArrastrable == ‘pieza1’)){
return false;
}
if ((id == ‘dos’) && (elemArrastrable == ‘pieza2’)){
return false;3
}
if ((id == ‘tres’) && (elemArrastrable == ‘pieza3’)){
return false;
}
if ((id == ‘cuatro’) && (elemArrastrable == ‘pieza4’)){
return false;
}
} else {
return true;
}
}
Hola Iván
Anda perfecto con este código
Muchísimas gracias
Saludos