Client-Side Image Compression on the Web
Tags: javascript, webdev, html, tutorial
Published: 2021-04-21 18:29:30

_This article was originally posted on [Medium](https://medium.com/swlh/boost-server-performance-with-client-side-image-compression-cdefba1c1c0d). If you prefer reading it from there, please do check it out._

*Huge shoutout to [Codú Community](https://www.youtube.com/watch?v=bXf_UdyDzSA) for inspiring this blog. All the code for this project is linked on [GitHub](https://github.com/Ramko9999/Medium-Image-Compression-Client-Side).*

## Introduction

There are multiple ways of optimizing server performance. 

One way is to make the client do some of the work. 

Consider uploading images for profile pictures. Since high-quality images take up several MB, it is costly to send them over the network to the server. Also, since profile pictures don’t need to be extremely detailed, it would be nice to compress them and save space. 

Thankfully, with HTML Canvas, we can compress our images on the client itself. After the compression, we can then send the images to the server, reducing upload time and the work the server must do.

## Agenda

* Setup demo HTML page

* Listen to the image input

* Resize and compress image with HTML Canvas

* Demo of the compression working

## Setup demo HTML page

To follow along, create a new project with the following files:

    - index.html
    - main.js

We will create the basic UI in `index.html`.

Put the following in `index.html`:

{% gist
https://gist.github.com/Ramko9999/6d1a76ba52d0eed58b4d5aacc3c09332

%}

In addition to accepting file uploads, we will preview both the initial image the user uploads and our compressed version in the UI.

Let’s go to `main.js` to handle when a user inputs an image.

## Listen to the image input

In `main.js`, let’s first define `getImageDimensions`, which returns a Promise of an input image’s width and height. We need the initial image’s dimensions to maintain the aspect ratio when we resize.

```javascript
    function getImageDimensions(image){
        return new Promise((resolve, reject) => {
            image.onload = function(e){
                const width = this.width;
                const height = this.height;
                resolve({height, width});
            }
        });
    }
```
Let’s now add an event listener to handle when our input tag, `image-input`, changes.

{% gist
https://gist.github.com/Ramko9999/8b83ee0a4db4899a5347d0a639a3f81a

%}

The above listener will trigger whenever a user uploads an image. We take the uploaded image, display it to the user, and acquire its dimensions. All that is left is to resize and compress the image.

## Resize and compress image with HTML Canvas

Let’s get to the fun part and make the `compressImage` function in `main.js`.

{% gist
https://gist.github.com/Ramko9999/c5c0492bd7fdcb0850ce207ebaa53a49

%}

**This is the magic!**

Given an HTML image, the scale factor, and the initial width and height of the image, the function creates an HTML Canvas and draws the image downscaled on it. 

Finally, we turn the downscaled image into a blob and resolve it from the Promise. The resolved blob represents our compressed image. 

We can now use this function to compress whatever image we want.

Let’s integrate this into the event listener we created earlier.

{% gist
https://gist.github.com/Ramko9999/b9342cb2303911c5525f5d6e9be7028d

%}

Let’s break this down.

First, we create two compressed images with differing scales: the ratio of `MAX_WIDTH` and the initial image’s width and the ratio of `MAX_HEIGHT` and the initial image’s height (You can parameterize `MAX_WIDTH` and `MAX_HEIGHT` based on the use case).

Then, we pick the smaller blob out of the two to be our compressed output and display it to the user. Finally, we check if our compressed version is smaller than the initial image. If the initial image was smaller, we can use it instead.

We now can compress images whenever the user inputs an image on the client. `optimalBlob` represents the image with the smallest size among both the compressed versions and the initial image.

## Demo of the compression working

![Photo by [Joshua Earle](https://unsplash.com/@joshuaearle) on [Unsplash](https://unsplash.com/photos/-87JyMb9ZfU)](https://cdn-images-1.medium.com/max/2000/0*7Zyw0voQ2rRuPgQ1)

I took the above image and submitted it into our file input.

Here is what occurred:

![Alt Text](https://media.giphy.com/media/QHJS7WRmL0vYTrgUUv/giphy.gif)

Here is the compressed result:

![Compressed Result](https://cdn-images-1.medium.com/max/2000/1*rYgb4t-aEgmjfd36MqG9EA.png)

The initial size of the image was roughly 299 KB and the compressed result was only 45 KB, a huge reduction. 

With this reduction in size, it will be much faster to send the image to the server, and the server doesn’t need to worry about compressing it either. 

**It is a win-win situation!** 

**However, if image quality is important, this approach is not a good idea, since resizing through HTML Canvas is lossy.** 

Despite that, this is a great way to handle the uploads of profile pictures.

**That’s all I got. Thanks for reading!**