Arrastrar y soltar en HTML5 (Drag & Drop HTML5)

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.

17 Comments

  1. Cristina Fernandez 10 mayo, 2013 Reply
    • Iván Salas 10 mayo, 2013 Reply
  2. gus mtz 1 junio, 2013 Reply
  3. Cristina Fernandez 10 junio, 2013 Reply
  4. Iván Salas 10 junio, 2013 Reply
    • anonimo 7 agosto, 2014 Reply
  5. Anonymous 11 junio, 2013 Reply
  6. CuboDeRubik 21 julio, 2014 Reply
  7. Lizbeth 3 septiembre, 2014 Reply
  8. Vianey 15 mayo, 2015 Reply
  9. maria 24 agosto, 2016 Reply
  10. emilio 25 mayo, 2017 Reply
    • Iván Salas 26 mayo, 2017 Reply
      • emilio 26 mayo, 2017 Reply
  11. Carlos Ramirez Kobra 1 noviembre, 2017 Reply
    • Iván Salas 1 noviembre, 2017 Reply
      • Carlos Ramirez Kobra 2 noviembre, 2017 Reply

Leave a Reply