Compressing Images Client-Side With JavaScript Before Upload

Publish date

In this short tutorial we'll learn how to compress images on the client before they're uploaded to the server, saving bandwidth, server space, and making the uploads faster.

What Is Image Compression?

Image compression is the act of making an image smaller in bytes. In reality this can be done lossless or lossy.

If lossless is our goal then we compress the images without losing image quality. Compression in this mode is limited as we can remove less information from the image to make it smaller. Lossless image compression can be done for example by removing metadata from the image. The image might be in a format that doesn't store image information optimally and by switching to a different format we don't lose information but the resulting image is a lot smaller.

In reality we're usually talking about lossy compression. Lossy compression means that we're making the image smaller by removing information (for example details or colors, but we could also scale the image to a smaller dimension).

What Are the Advantages Of Compressing Images On The Client?

Compressing images on the client is useful for multiple reasons:

How To Compress Images On The Client?

We can compress images on the client using the HTML5 canvas API.

<input id="image-upload" type="file" accept="image/*" />

<script>
    const input = document.querySelector('#image-upload');

    input.addEventListener('change', handleChange);

    function fileToImage(file) {
        return new Promise((resolve, reject) => {
            const image = new Image();
            const url = URL.createObjectURL(file);
            image.onload = () => {
                resolve(image);
            };
            image.onerror = reject;
            image.src = url;
        });
    }

    function canvasToBlob(canvas, type, quality) {
        return new Promise((resolve, reject) => {
            canvas.toBlob(
                (result) => {
                    if (result) {
                        resolve(result);
                    } else {
                        reject(new Error('canvasToBlob failed'));
                    }
                },
                type,
                quality
            );
        });
    }

    async function handleChange() {
        const file = input.files?.[0];
        if (!file) {
            return;
        }

        // turn the file into an image
        const image = await fileToImage(file);

        // draw the image to a canvas
        const canvas = document.createElement('canvas');
        canvas.width = image.naturalWidth;
        canvas.height = image.naturalHeight;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0);

        // compress the image
        const blob = await canvasToBlob(canvas, 'image/jpeg', 0.75);

        // set the file to the input
        const files = new DataTransfer();
        files.items.add(new File([blob], file.name, { type: blob.type }));
        input.files = files.files;
    }
</script>

Of course this is Web Development so there are a million headache-inducing caveats to take into account, there's simply no place to address those in this article.

The above caveats are fun to figure out as a hobby, but if you need image compression for a professional project then maybe you don't have the time. Instead it might be a smart idea to take a look at Pintura, it addresses all these issues, and should something still go wrong, there's a professional team to fall back to.