El evento drag

facebook-svg gplus-svg twitter-svg

Hoy en día podemos arrastrar y soltar un archivo de nuestro ordenador directamente en el navegador, y dejar al navegador que utilice este archivo, que puede ser una imagen, o un documento de texto, u otro tipo de archivos.
A continuación quiero explicar un caso en concreto: quiero coger una imagen que tengo en el escritorio y arrastrarla dentro de un elemento <canvas>. El <canvas> recupera la imagen, la modifica y la vuelve a poner ( con putImageData() ) en el <canvas>. Al final utilizando el método toDataURL() recupera el data:uri de la nueva imagen y la vuelve a utilizar.

Para empezar en el HTML tengo un <canvas> de 300px / 300px con un borde de líneas discontinuas y un párrafo que dice "suelte aquí una imagen",  que está justo debajo del <canvas>.


<div id="dropzone">
<canvas></canvas>
<p class="canvasp">Suelte una imagen aquí</p>
</div>
<p id="dataUri"></p>		

El CSS es nada complicado, minimalista.


#dropzone {
  display: block;
  width: 300px;
  height: 300px;
  position: relative;
  margin: 50px auto 0;
  padding: 0;
}

canvas {
  border: 1px dashed;
  font-size: 2em;
  width: 300px;
  height: 300px;
}
p{word-wrap: break-word;text-align: center;}
p.canvasp {
  display: block;
  width: 300px;
  position: absolute;
  top: 134px;
  z-index: -1;
}

Si arrastramos una imagen de nuestro ordenador en el navegador esta se abre en una página distinta. ¡Compruébelo!

Y no nos interesa que esto pase. Así que tenemos que impedir al navegador que haga lo que hace por defecto. Para esto utilizamos los métodos event.stopPropagation() y event.preventDefault().

Los eventos a utilizar en este caso son:
- dragenter ( cuando la imagen que arrastramos entra en el canvas ),
- dragover ( cuando arrastramos la imagen por encima del canvas ), y
- drop ( cuando soltamos la imagen ).

Pero hay toda una lista de eventos que pueden ser utilizados en esta situación.


  canvas.addEventListener("dragenter", dragenter, false);
  canvas.addEventListener("dragover", dragover, false);
  canvas.addEventListener("drop", drop, false);
  
function dragenter(e) {
    e.stopPropagation();
    e.preventDefault();
  }
  
function dragover(e) {
    e.stopPropagation();
    e.preventDefault();
  }
  
function drop(e) {
    e.stopPropagation();
    e.preventDefault();
  }

 

Véalo en codepen

See the Pen Drag your file here A by Gabi (@enxaneta) on CodePen.

Ahora si soltamos la imagen justo encima del canvas no pasa nada, y es justo lo que queremos. Sin embargo si soltamos la imagen fuera del canvas el navegador abrirá la imagen en otra página.

Para poder manejar los archivos necesitamos saber más, y para esto tenemos recuperar los datos de esta imagen, y esto es relativamente fácil. El objeto dataTransfer guarda los datos de los archivos que soltamos en el canvas, y dataTransfer.files contiene una lista de los archivos soltados.

function drop(e) {
    e.stopPropagation();
    e.preventDefault();
  
    var datos = e.dataTransfer;
    var archivos = datos.files;
    // ahora podemos manejar los archivos:
    manejarArchivos(archivos);
  }

La función manejarArchivos()

Primero utilizamos un bucle for para iterar todos los archivos soltados en el canvas.

function manejarArchivos(archivos) {
for (var i = 0; i < archivos.length; i++) {
      var archivo = archivos[i];
      . . . . .

Para cada archivo necesitamos comprobar si es una imagen. El MIME Type de un archivo de imagen empieza con "image/" asi que podemos utilizar expresiones regulares para verificar si se trata de una imagen o no:

// si es una imagen empieza con "image/"
var esImagen = /^image\//;      
// si no esImagen ignoralo
if (!esImagen.test(archivo.type)) {
        continue;
 }

Pero si es una imagen queremos crear un nuevo objeto imagen:

var img = new Image();

El atributo src de esta imagen es:

img.src = window.URL.createObjectURL(archivo);

Lea acerca del método URL.createObjectURL()

Y cuando la imagen esté cargada ( img.onload ) podremos hacer un montón de cosas, pero por ahora solo quiero dibujarla en el canvas: ctx.drawImage(this, 0, 0);
Al final llamamos el método URL.revokeObjectURL() para comunicar al navegador que hemos acabado y ya no es necesario guardar la información acerca del archivo en memoria.

img.onload = function() {
ctx.drawImage(this, 0, 0);
window.URL.revokeObjectURL(this.src);
}

function manejarArchivos(archivos) {
    for (var i = 0; i < archivos.length; i++) {
      var archivo = archivos[i];
      
      var esImagen = /^image\//;
      
      if (!esImagen.test(archivo.type)) {
        continue;
      }
      
      var img = new Image(); 
      img.src = window.URL.createObjectURL(archivo);
      img.onload = function() {
     
      ctx.drawImage(this, 0, 0);
      
      window.URL.revokeObjectURL(this.src);     
      }
   }
}

 

Véalo en codepen

See the Pen Drag your file here B by Gabi (@enxaneta) on CodePen.

Centrar la imagen

Para centrar la imagen en el canvas necesitamos conocer las coordenadas del centro del canvas (cx y cy):

var cw = canvas.width = 300,
    cx = cw / 2;
var ch = canvas.height= 300,
    cy = ch / 2; 

y la anchura y la altura de la imagen:

var w = img.width;
var h = img.height;

Ahora podemos dibujar la imagen con drawImage() y centrarla:

img.onload = function() {
      var w = img.width;
      var h = img.height;
      // centrar la imagen y dibujarla 
      ctx.drawImage(this, cx - w/2, cy - h/2, w, h);
      // ya no es necesario guardar la información acerca del archivo en memoria
      window.URL.revokeObjectURL(this.src);     
}

El negativo de una imagen

Además de centrar la imagen podemos hacer más cosas, como por ejemplo manipular la imagen. En este caso he decidido poner la imagen en negativo.


function negativo(ctx,cw,ch){
   //Devuelve un objeto imgData con los datos de todos los píxeles de la imagen
   var imgData = ctx.getImageData(0, 0, cw, ch);
   var pixels = imgData.data;
   // recorre uno a uno los pixeles de la imagen y cambia el color por el complementario
   for (var i = 0; i < pixels.length; i += 4) {
      pixels[i] = 255 - pixels[i]; // rojo
      pixels[i + 1] = 255 - pixels[i + 1]; // verde
      pixels[i + 2] = 255 - pixels[i + 2]; // azul
    }
    // pone la imagen modificada en el canvas
      ctx.putImageData(imgData, 0, 0, 0, 0, cw, ch)
}

También podemos recuperar el data:uri de esta imagen, y muchisimas más cosas

img.onload = function() {
   var w = img.width;
   var h = img.height;
   // centrar la imagen  
   ctx.drawImage(this, cx - w/2, cy - h/2, w, h);
   //pone la imagen en negativo
   negativo(ctx,cw,ch);
   // recupera el data:uri de la imágen
   url = canvas.toDataURL();
   outputDataUri.innerHTML = url;
   window.URL.revokeObjectURL(this.src);

 

Vea este ejemplo en codepen:

See the Pen Drag your file here C by Gabi (@enxaneta) on CodePen.