Coopérative numérique Corse
drag'n drop

[Mini-Tuto] Compression d'images côté client en drag'n drop

Avec HTML et Javascript

Ce mini-tutoriel a pour but de présenter une manière de créer un système capable de compresser (ou plutôt de redimensionner) des images côté client, avant que celles-ci ne soient envoyées au serveur. Ceci permettant de gagner considérablement en temps de transfert réseau et d'éviter que votre serveur ne doivent traiter des images qui pourraient atteindre un poids assez considérable en fonction de l'appareil qui a effectué la capture.


Disposition graphique

Avant de commencer, nous allons avoir besoin d'une structure html/css simple contenant les différents éléments visuels :

index.html :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Tuto compression drag'n drop</title>
        <link rel='stylesheet' href='style.css'>
        <script src='script.js'></script>
        <script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
    </head>
    <body>
        <div id='drop'>Faites glisser vos photos ici</div>
        <div id='info'></div>
        <div id='thumbs'></div>
        <button id='btn'>ENVOYER LES PHOTOS</button>
        <canvas id='canvas'></canvas>
    </body>
</html>

style.css :

html, body {
    background: #0a0808;
    color: white;
    text-align: center;
    font-size: 130%;
}

body > * {
    margin: auto;
}

button {
    color: white;
    background: rgba(0, 0, 0, 0.2);
    border-radius: 5px;
    padding: 10px;
    margin: auto;
    margin-top: 5px;
    cursor: pointer;
    font-size: 100%;
    display: none;
}

#drop {
    box-sizing: border-box;
    width: 100%;
    height: 300px;
    border: 5px #978282 dotted;
    color: #504a4a;
    padding-top: 100px;
    font-size: 170%;
}

#thumbs img {
    width: 100px;
    height: 100px;
    margin: 5px;
}

canvas {
    display: none;
}

À partir de là, votre page devrait ressembler à ceci :

Notre page HTML contient divers éléments graphiques qui sont masqués pour le moment (tel que le bouton et le canvas), mais qui nous seront utiles par la suite. La div info permettra d'afficher à l'utilisateur des messages concernant l'état du traitement de ses images. Et nous utiliserons la div thumbs pour afficher des miniatures des images traitées.

Préparation des événements

Afin de pouvoir gérer le drag'n drop, nous allons avoir besoin de créer diverses fonctions qui s’exécuteront lorsqu'un événement se produit.

Modifications du fichier index.html :

<script>
    function load() {
        var drop = document.getElementById('drop');
        drop.addEventListener('drop', drop_handler, false);
        drop.addEventListener('dragenter', dragenter_handler, false);
        drop.addEventListener('dragover', dragenter_handler, false);         
        drop.addEventListener('dragleave', dragleave_handler, false);
    }
</script>
sans oublier de faire en sorte que la fonction load ci-dessus soit appelée au chargement de la page :
<body onload='load();'>

Le fichier index.html ressemble donc à ceci :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Tuto compression drag'n drop</title>
        <link rel='stylesheet' href='style.css'>
        <script src='script.js'></script>
        <script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
        <script>
            function load() {
                var drop = document.getElementById('drop');
                drop.addEventListener('drop', drop_handler, false);
                drop.addEventListener('dragenter', dragenter_handler, false);
                drop.addEventListener('dragover', dragenter_handler, false);
                drop.addEventListener('dragleave', dragleave_handler, false);
            }
        </script>
    </head>
    <body onload='load();'>
        <div id='drop'>Faites glisser vos photos ici</div>
        <div id='info'></div>
        <div id='thumbs'></div>
        <button id='btn'>ENVOYER LES PHOTOS</button>
        <canvas id='canvas'></canvas>
    </body>
</html>

Ce que nous venons de faire, c'est simplement gérer les événements qui se produiront sur la div drop, c'est à dire :

  • Lorsqu'un événement de type drop se produit, on exécute la fonction drop_handler
  • Lorsqu'un événement de type dragenter se produit, on exécute la fonction dragenter_handler
  • Lorsqu'un événement de type dragover se produit, on exécute la fonction dragenter_handler
  • Lorsqu'un événement de type dragleave se produit, on exécute la fonction dragleave_handler

L'événement drop représente l'action de lâcher un fichier dans la zone.
Les événements dragenter et dragover se produisent lorsque le curseur de votre souris rentre dans la zone avec un ou plusieurs fichiers.
Et l'événement dragleave représente l'action de quitter la zone de drag'n drop.
Implémenter les fonctions répondant aux événements

Vous l'aurez compris, on a définit quelles sont les fonctions qui seront appelées lorsque différents événements se produisent, mais il nous reste encore à faire l'implémentation de ces différentes fonctions.

script.js

// Événement "dragenter" et "dragover"
function dragenter_handler(e) {
    e.stopPropagation();
    e.preventDefault();
    this.style.border = '5px white dotted';
}

// Événement "dragleave"
function dragleave_handler(e) {
    e.stopPropagation();
    e.preventDefault();
    this.style.border = '5px #978282 dotted';
}

// Événement "drop"
function drop_handler(e) {
    e.stopPropagation();
    e.preventDefault();
    var files = e.target.files || e.dataTransfer.files;
    for (var i = 0; i < files.length; i++) {
        process_file(files[i]);
    }
    this.style.border = '5px #978282 dotted';
}

Toutes ces fonctions prennent en paramètre un objet représentant l'événement qui se produit (ici appelé e) et les deux premières instructions de ces fonctions permettent juste d'éviter de propager l'événement à une autre fonction.
Les fonctions dragenter_handler et dragleave_handler sont assez simples, elles se contentent tout simplement de changer le style des pointillés autour du cadre lorsque la souris rentre ou sort de celui-ci : Et pour ce qui est de la fonction drop_handler, celle-ci va appeler une autre fonction que j'ai nommé process_file pour chaque fichier qui a été déposé.

Le traitement d'un fichier

La fonction process_file va devoir effectuer plusieurs choses pour pouvoir traiter le fichier :

// Fonction permettant le traitement d'une image
function process_file(file) {
    // Vérification du type de fichier déposé
    if (file.type !== 'image/jpeg') {
        return;
    }

    // Affichage d'un message pour l'utilisateur
    document.getElementById('info').innerHTML = 'Compression en cours, veuillez patienter...';

    // Création d'un "conteneur" pour l'image à traiter
    var img_to_resize = document.createElement('img');
    img_to_resize.src = window.URL.createObjectURL(file);

    // Création d'un "conteneur" pour la miniature à afficher
    var img = document.createElement('img');
    img.src = window.URL.createObjectURL(file);

    // On affiche la miniature sur la page web
    document.getElementById('thumbs').appendChild(img);
    window.URL.revokeObjectURL(file);

    // Lorsque l'image à traiter à été complètement chargée
    img_to_resize.onload = function () {
        // On commence son redimensionnement
        resize_image(this);
        // Puis on change le message pour l'utilisateur
        document.getElementById('info').innerHTML = 'Compression terminée !';
        // Et on affiche le bouton "envoyer"
        document.getElementById('btn').style.display = "block";
    };
}
Tout d'abord, nous vérifions que le fichier est bel et bien une image, ici de type jpeg uniquement. Et nous affichons un message à l'utilisateur pour lui indiquer qu'un traitement est en cours : Ensuite, nous créons dynamiquement 2 balises images, la première nous permettra de conserver l'image pour son traitement et la deuxième est simplement ajoutée dans le bloc thumbs de notre page web pour afficher une miniature.
Lorsque notre image img_to_resize sera complètement chargée, nous appellerons une fonction resize_image qui effectuera son redimensionnement avant d'indiquer à l'utilisateur que la compression a été terminée et ainsi rendre visible le bouton d'envoi.
Le redimensionnement d'une image

La fonction resize_image utilisera la balise canvas, que l'on a déclaré dans notre fichier html mais que l'on a masqué à l'utilisateur, afin de pouvoir redimensionner une image :

// Variable qui conservera en mémoire la liste des images traitées
var data = [];

// Fonction permettant le redimensionnement d'une image
function resize_image(img) {
    // On accède à notre canvas
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext("2d");

    // On dessine notre image dans notre canvas
    ctx.drawImage(img, 0, 0);

    // On fixe les dimensions maximales que l'on souhaite pour notre image en sortie
    var MAX_WIDTH = 1400;
    var MAX_HEIGHT = 1200;

    // On récupère les dimensions actuelles de notre image
    var width = img.width;
    var height = img.height;

    // On effectue divers calculs pour obtenir les dimensions de sortie
    // tout en conservant le ratio largeur/hauteur de notre image
    // afin d'éviter une éventuelle déformation de celle-ci
    if (width > height) {
        if (width > MAX_WIDTH) {
            height *= MAX_WIDTH / width;
            width = MAX_WIDTH;
        }
    } else {
        if (height > MAX_HEIGHT) {
            width *= MAX_HEIGHT / height;
            height = MAX_HEIGHT;
        }
    }

    // On redimensionne notre canvas selon les dimensions calculées
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(img, 0, 0, width, height);

    // On exporte l'image traitée dans notre liste d'images
    var dataurl = canvas.toDataURL("image/jpeg");
    data.push(dataurl);
}
Vous l'aurez compris, cette fonction utilise un canvas qui est présent mais caché dans la page comme intermédiaire afin de permettre le redimensionnement de l'image.
Chaque fois que cette fonction sera appelée, la variable data contiendra une image supplémentaire.
Voici un exemple ce que l'on peut obtenir visuellement à la fin d'un traitement : Il ne manque à présent plus qu'une chose ! L'envoi des images lors de l'appui sur le bouton ENVOYER LES PHOTOS.
Bouton "envoyer"

Afin d'envoyer les images, il nous faut créer une fonction qui répondra à l'événement du click sur le bouton et lier cet événement au bouton correspondant.
J'ai appelé cette fonction send_data, nous allons donc commencer par modifier notre fichier index.html pour ajouter cet événement :

<button id='btn' onclick='send_data();'>ENVOYER LES PHOTOS</button>
Ensuite, nous implémentons cette fonction :
function send_data() {
    // On envoi la première image de notre liste
    send_element(0);
}

function send_element(index) {
    if (index < data.length) { // Si il reste des images à envoyer
        
        // Affichage d'un message pour l'utilisateur indiquant le nombre d'images déjà envoyées
        document.getElementById('info').innerHTML = 'Transfert en cours : ' + index + ' / ' + data.length + ', veuillez patienter...';

        // Envoi de l'image
        $.ajax({
            url: 'VOTRE_URL_SCRIPT_SERVEUR_ICI',
            type: 'post',
            data: {
                files: [data[index]] // On envoi l'image numéro [index]
            },
            success: function () {
                // Lorsque l'image a été envoyée, on passe à la suivante
                // en rappelant cette fonction avec le numéro suivant
                send_element(index + 1);
            }
        });
    } else { // S'il n'y a plus d'image à envoyer
        
        // On affiche un message à l'utilisateur
        document.getElementById('info').innerHTML = 'Transfert terminé !';
        // On vide la liste des miniatures affichées
        document.getElementById('thumbs').innerHTML = '';
        // On masque le bouton d'envoi
        document.getElementById('btn').style.display = 'none';
        // Et on vide la variable contenant les images traitées
        data = [];
    }
}
Dans cette fonction, data.length représente le nombre d'images qui doivent être envoyées au serveur.
La fonction send_element sera appelée autant de fois qu'il y a d'images, en commençant par l'image numéro 0: send_element(0), send_element(1), send_element(2), etc... : Quand toutes les images ont été envoyées, on affiche un message et on vide les données qui ne sont plus nécessaires :
Pour finir

Le but de ce tutoriel était principalement de vous montrer qu'il est possible de redimensionner des images directement depuis votre navigateur web, mais sachez qu'il existe certainement d'autres façons de faire.
Pour l'envoi des fichiers, j'ai utilisé la fonction ajax de jQuery en envoyant mes images via un paramètre que j'ai appelé files mais on aurait très bien pu imaginer une autre logique.
Pour le redimensionnement, je suis passé par un canvas mais sachez qu'il existe certainement des librairies javascript qui permettent de le faire différemment.
Et pour finir j'espère que cette petite introduction au sujet du drag'n drop vous sera utile.

Profile picture for user Damien
Damien
Grandi
Développeur full-stack
Titulaire d’un master en systèmes d’information et internet, il conçoit un système informatique de A à Z, des applications web fluides et accessibles. Sa double compétence en géomatique lui permet de créer des applications de cartographie utilisant Google Maps ou OpenLayers. Il leur confère une valeur supplémentaire en y intégrant le traitement et l’exploitation des données recueillies. Il rejoint l'équipe d'Icare Project en 2017 et continue d'apporter son savoir-faire aux membres de C4C en tant que membre honoraire.