• JAVASCRIPT > Compresser les images dans le navigateur avant un upload

      Comment compresser une image 2000×3000 en 200×300 depuis le navigateur avant un upload ?

       

      Marche à suivre :

      1. on récupère l’image depuis un <input type="file">,
      2. on convertit l’image en HTMLImageElement afin de pouvoir l’utiliser avec canvas
      3. on « affiche » l’image dans le canvas aux dimensions voulues (le canvas sera invisible)
      4. on retourne l’image en base64 qui pourra être directement fournie en tant que src d’une balise img et envoyée au serveur.

      On passera par une étape intermédiaire pour obtenir les dimensions réelles de l’image afin d’en calculer le ratio.

      Convertir l’image en base64

      Commençons par créer la fonction permettant de convertir l’image en base64.

       

      function convertFileToB64(file, callback) {
      
         // on créé un object fileReader.
         // Cela permet de lire le contenu des fichiers
         const reader = new FileReader();
      
         // dès que le fichier est chargé, on envoie le résultat
         reader.addEventListener('load', () => {
            callback(reader.result);
         }, false);
      
         // on précise que l'on veut se fichier sous la forme d'une dataURL (base64)
         reader.readAsDataURL(file);
      }

       

      Cette fonction prend en entrée un fichier de type file, tel que récupéré depuis un champ de sélection de fichier. Comme dans l’exemple ci-dessous.

       

      // on imagine que notre champ a l'id="upload-image"
      const inputFile = document.getElementById('upload-image');
      
      // il faut écouter les changements pour savoir quand un fichier est sélectionné
      inputFile.onchange = function(event) {
         const img = inputFile.files[0];
      }

      Obtenir un objet image exploitable

      Dans un second temps, on va créer la fonction permettant d’obtenir la résolution de l’image en pixels. Par ailleurs, canvas ne comprend pas le base64, on va donc le nourrir d’HTMLImageElement.

       

      function getImage(b64img, callback) {
      
         // créé un objet HTMLImageElement vide
         const imgObj = new Image();
      
         // dès que l'image est chargée, on appelle le callback
         imgObj.onload = () => {
            callback(imgObj);
         };
      
         // on lui donne l'image source
         imgObj.src = b64img;
      }

       

      Cette fonction prend en entrée une image encodée en base64, ça tombe bien, nous avons déjà créé la fonction qui nous permet de faire ça ! Elle renvoie dans le callback un objet de type HTMLImageElement depuis lequel nous pourrons accéder aux propriétés de l’image.

      Redimensionnement

      Histoire d’avoir une fonction un peu souple, on proposera deux formats de sortie : le jpeg et le png. On donnera également la possibilité de forcer les dimensions, c’est à dire que le ratio d’origine sera ignoré et que l’image sera redimensionnée précisément aux valeurs données en paramètres.

      Notre fonction acceptera les paramètres suivants :

      • img: c’est un objet image tel que récupéré depuis le champ <input type="file">
      • options:
        • {string} outputFormat – (jpe?g ou png)
        • {string} targetWidth – largeur désirée
        • {string} targetHeight – hauteur désirée
        • {bool} forceRatio – s’il est à true, on force les dimensions même si cela déforme l’image
      • callback
      function resize(img, options, callback) {
      
         // on s'assure que l'image est bien en jpeg ou png
         // on se sert ici d'une REGEX pour plus de concision
         if (!/(jpe?g|png)$/i.test(img.type)) {
            callback(new TypeError('image must be either jpeg or png'));
            return;
         }
      
         // on vérifie que le format de sortie est supporté (jpeg, jpg ou png)
         if (options.outputFormat !== 'jpg' && options.outputFormat !== 'jpeg' && options.outputFormat !== 'png') {
            callback(new Error('outputFormat must be either jpe?g or png'));
            return;
         }
      
         // on définit le format
         const output = (options.outputFormat === 'png') ? 'png' : 'jpeg';
      
         // on appelle nos petites fonctions bien pratiques ;)
         convertFileToB64(img, (b64img) => {
            getImage(b64img, (imgObj) => {
               // prépare le canvas et on récupère les dimensions de l'image
               const canvas = document.createElement('canvas');
               const context = canvas.getContext('2d');
               const imgWidth = imgObj.width;
               const imgHeight = imgObj.height;
               let width;
               let height;
      
               // calcul du ratio
               // L'image ne doit pas dépasser les dimensions fournies
               // donc si la longueur et la hauteur ne correspondent pas au ratio
               // on ajuste l'image au ratio tout en ne dépassant par la largeur ou hauteur
               if (!options.forceRatio) {
                  if (imgWidth > imgHeight) {
                     width = options.targetWidth;
                     height = Math.round((imgHeight / imgWidth) * width);
                  }
      
                  else {
                     height = options.targetHeight;
                     width = Math.round((imgWidth / imgHeight) * height);
                  }
               }
      
               // pas de calcul du ratio si l'option forceRatio est à true
               else {
                  width = options.targetWidth;
                  height = options.targetHeight;
               }
      
               // on donne ses dimensions au canvas
               canvas.width = width;
               canvas.height = height;
      
               // on dessine l'image
               context.drawImage(imgObj, 0, 0, width, height);
      
               // on exporte l'image au format souhaité en base64
               // directement dans le callback
               callback(null, canvas.toDataURL(`image/${output}`));
            });
         });
      }

       

      Pour faire les choses proprement, le mieux est d’encapsuler ce code dans un module pour ne pas polluer l’espace global avec des fonctions inutiles (elles ne servent que dans le cadre de resize).

      Code complet sur Github avec toutes les informations pour l’installer.

       

      De plus, dans la version Github, j’ai ajouté la possibilité de recadrer de manière intelligente. Par exemple, si le format de destination est carré mais que la photo source est au format portrait (ou paysage), l’image sera automatiquement croppée sur le visage !

 

Aucun commentaire

 

Laissez un commentaire