Envoyer et redimensionner une image grâce au HTML5

Si vous développez une application web utilisant l'AJAX pour éviter le rechargement des pages, vous avez surement été confronté à des difficultés pour envoyer un fichier sur un serveur. En effet, avant l'arrivé du HTML5, la solution était soit, d'utiliser un plug-in tel que Flash, soit, d'utiliser une iframe pour simuler l'envoi d'un formulaire.

Aujourd'hui, grâce au HTML5, on peut non seulement envoyer des fichiers sans plug-in et sans iframe, mais on peut également redimensionner des images avant leurs envois sur le serveur. De quoi gagner du temps en développement et économiser de la bande passante.

Je vais dans cet article vous présenter les différentes API offertes par HTML5 permettant de se simplifier la vie.

Sélectionner une image

Pour que vos utilisateurs puissent envoyer une image sur vos serveurs, ils doivent avant tout pouvoir la sélectionner sur leur ordinateur. Pour cela, la méthode la plus commune est d'ajouter une balise <input> de type file :

  <input type="file" name="file" id="input-file" />

Il est inutile ici d'ajouter une balise <form> étant donné que l'on va utiliser JavaScript pour intercepter les changements et envoyer le fichier.

Pour cela, il faut observer les événements sur notre balise <input>. Le code javascript suivant permet de lancer la fonction processFiles à chaque fois qu'un utilisateur sélectionne un fichier.

  // Lorsque des modifications sont effectuées sur notre champ input
  $('#input-file').change(function(evt) { 
    // On récupère le fichier sélectionné
    var file = evt.target.files[0];

    // Et on lance la fonction processFiles qui va redimensionner et envoyer le fichier sur le serveur
    processFiles(file);
  });

Le Drag and Drop

Grâce au HTML5 la balise <input> n'est plus la seule solution pour selectionner un fichier. On peut maintenant utiliser le glisser-déposer. Pour cela il suffit d'ajouter une <div> que l'on définira comme notre drop zone.

  <div id="drop-zone" />

Une fois ceci fait, il faut observer les événements sur notre drop zone :

  
  var $dropZone = $('#drop-zone'),
      dropZone = $dropZone[0];

  // Voici les principaux événements disponibles
  dropZone.addEventListener('dragenter', dragEnter, false);
  dropZone.addEventListener('dragleave', dragLeave, false);
  dropZone.addEventListener('drop', drop, false);

  // Lorsqu'un fichier est glissé sur la drop zone
  dragEnter = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    
    // On ajoute par exemple la class «drag» qui modifiera l'apparence de la drop zone
    $dropZone.addClass('drag');
  };

  // Lorsqu'un fichier est enlevé de la drop zone
  dragLeave = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    
    $dropZone.removeClass('drag');
  };

  // Lorsqu'un fichier est lâché sur la drop zone
  drop = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    
    $dropZone.removeClass('drag');
    
    // On lance la fonction processFiles qui va redimensionner et envoyer le fichier sur le serveur
    processFiles(evt.dataTransfer.files[0]);
  };

D'autres événements tels que dragstart, dragover et dragend sont également disponibles.

Que ce soit via l'API drag and drop ou une balise <input>, une fois le fichier sélectionné, la fonction processFiles est lancée avec comme paramètre le fichier choisi par l'utilisateur. Voyons maintenant comment traiter ce fichier.

Redimensionner une image

Pour redimensionner une image, HTML5 nous offre l'objet FileReader et l'élément canvas. L'idée est qu'une fois l'image sélectionnée par l'utilisateur, on utilise l'objet FileReader pour lire le contenu du fichier et l'encoder en base64.

  processFiles = function(file) {
    var reader = new FileReader();

    // Lorsque le fichier aura été entièrement lu, la fonction resizeImage sera lancée
    reader.onloadend = function(evt) {
      // reader.result représente notre fichier encodé en base64
      that.resizeImage(reader.result, file);
    };
    
    // Permet de lancer la lecture du fichier
    reader.readAsDataURL(file);
  };

Une fois le fichier encodé en base64, on le charge dans une balise <img> pour qu'il soit interprété comme une image par le navigateur. C'est à partir de ce moment-là que l'on va pouvoir utiliser l'élément canvas pour redimmensionner l'image.

  resizeImage = function(data, file) {
    var fileType = file.type,
        maxWidth = 960,
        maxHeight = 960;
    

    // On charge le fichier dans une balise <img>
    var image = new Image();
    image.src = data;

    // Une fois l'image chargée, on effectue les opérations suivantes
    image.onload = function() {
      // La fonction imageSize permet de calculer la taille finale du fichier en conservant les proportions
      var size = imageSize(image.width, image.height, maxWidth, maxHeight),
          imageWidth = size.width,
          imageHeight = size.height,

          // On créé un élément canvas 
          canvas = document.createElement('canvas');
          
      canvas.width = imageWidth;
      canvas.height = imageHeight;

      var ctx = canvas.getContext("2d");

      // drawImage va permettre le redimensionnement de l'image
      // this représente ici notre image
      ctx.drawImage(this, 0, 0, imageWidth, imageHeight);

      // Permet d'exporter le contenu de l'élément canvas (notre image redimensionnée) au format base64
      data = canvas.toDataURL(fileType);
      
      // On supprime tous les éléments utilisés pour le redimensionnement
      delete image;
      delete canvas;
      
      submitFile(data);
    }
  };


  // Fonction permettant de redimensionner une image en conservant les proportions
  imageSize = function(width, height, maxWidth, maxHeight) {
    var newWidth = width, 
        newHeight = height;
    
    if (width > height) {
      if (width > maxWidth) {
        newHeight *= maxWidth / width;
        newWidth = maxWidth;
      }
    } else {
      if (height > maxHeight) {
        newWidth *= maxHeight / height;
        newHeight = maxHeight;
      }
    }

    return { width: newWidth, height: newHeight };
  };

Envoyer l'image

Notre image étant encodée en base64, il suffit simplement de l'envoyer via une requête AJAX classique.

  submitFile = function(data) {
    $.ajax({
      url: 'url-vers-votre-serveur',
      // Voici un exemple en utilisant JSON
      data: JSON.stringify({ file: data }),
      type: 'POST',
      success: function(response) {} // Un éventuel callback
    });
  }

Côté serveur si vous utilisez PHP, vous pouvez utiliser cette fonction pour récupérer le contenu de la requète AJAX et décoder le contenu encodé au format JSON

  function getPayload() {
    if(isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {
      $httpContent = fopen('php://input', 'r');
      $data = stream_get_contents($httpContent);
      fclose($httpContent);
  
      return json_decode($data, true);
    }
    else return false;
  }

Envoyer un fichier brut

Dans l'exemple précédent, notre fichier étant encodé en base64, il peut être envoyé facilement via une requète AJAX. Cependant, si votre fichier n'est pas une image ou que vous ne souhaitez pas utiliser l'objet FileReader, l'objet FormData également disponible grace au HTML5 vous sera d'un grand secours.

FormData

l'interface FormData est disponible grace à XMLHttpRequest 2. Cet objet permet de créer et d'envoyer un formulaire directement en javascript.

Voici comment l'utiliser :

  // Création du formulaire
  var formData = new FormData();

  // Ajout de clés/valeurs
  formData.append("fileName", 'Le titre du fichier');
  formData.append("file", file); // Le fichier brut

  // Vous pouvez envoyer la requète en utilisant jQuery
  $.ajax({
    url: 'url-vers-votre-serveur',
    data: formData,
    processData: false, // Afin que jQuery ne transforme pas les données
    contentType: false, // Afin que jQuery n'ajoute pas de Content-Type
    type: 'POST',
    success: function(response) { } // Un éventuel callback
  });

Dans ce cas, côté serveur, pour récupérer notre fichier en PHP, il suffit d'utiliser la variable de téléchargement de fichier :

  $fileName = $_POST['fileName'];
  $file = $_FILES['file'];



Vous connaissez maintenant les bases pour envoyer des fichiers en HTML5.

Afficher l'article