-
JAVASCRIPT > Compresser les images dans le navigateur avant un upload
- on récupère l’image depuis un
<input type="file">
, - on convertit l’image en
HTMLImageElement
afin de pouvoir l’utiliser aveccanvas
- on « affiche » l’image dans le canvas aux dimensions voulues (le canvas sera invisible)
- on retourne l’image en base64 qui pourra être directement fournie en tant que
src
d’une baliseimg
et envoyée au serveur. 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
Comment compresser une image 2000×3000 en 200×300 depuis le navigateur avant un upload ?
Marche à suivre :
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 :
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 !
- on récupère l’image depuis un