JavaScript image compression and resizing

Uploading and downloading images is a very common feature in modern web applications but exchanging files between client and server can quickly become a high resource consuming task. We must also consider that most Internet traffic comes from mobile devices, so we can expect users to upload photos taken with their phones. Those files can be very heavy (> 10MB) because of the ever increasing camera resolution on new mobile devices.

Sharing images in your platform means that users upload their photos to your storage server and then other users download those photos to use them somehow. This task involves much more resources compared to storing a new record in the database. We can expect an higher cost in terms of:

  • Upload bandwidth.
  • Download bandwidth. In a typical use case, there are many downloads for each image uploaded.
  • Storage. Photos and files are typically stored in a disk or in some object storage service. It’s important to note that once you save a photo in your storage you must keep it stored throughout the lifetime of the software unless you apply some deletion policy. For this reason storage costs always increase during time, in contrast to bandwidth costs that depend on current usage.

Due to the pandemic emergency for COVID 19, in the period between March and June 2020, Nuvola has become the main hub for teachers, pupils and parents. This situation results in a rapid increase in traffic, as we have already talked about in a previous article. Furthermore, the needs of schools have changed to address distance learning. For example, students should send homework to teachers and teachers should send corrections back. Until now, this functionality was not needed because this process was done physically in the classroom. This new feature clearly implies file sharing. Talking with our customers we discovered that users prefer to do their homework in their exercise book, take a picture and share it on the platform. This means that most of the shared files are images and, for this reason, the benefit of image compression will be really huge.

How can image sharing be optimized?

The obvious answer is image compression. However, if image quality is your software primary concern, this technique is probably not right for you. A common solution involves server-side compression, reducing download bandwidth and storage space required. However this approach leads to increased CPU cycles which means an additional cost, even though probably less expensive than download bandwidth.

Thanks to modern browser API we can also reduce unnecessary upload bandwidth compressing images client-side, before uploading them. Reducing bandwidth also means a faster upload because compression time is much smaller than a large file upload request over network.

HTML5 features such as Canvas, FileReader and Blob allow compressing images directly in the browser, resulting in a lower number of bytes the platform needs to upload, store and download.

A little bit of context from MDN

The Canvas API provides a means for drawing graphics via JavaScript and the HTML <canvas> element. Among other things, it can be used for animation, game graphics, data visualization, photo manipulation, and real-time video processing. The Canvas API largely focuses on 2D graphics. The WebGL API, which also uses the <canvas> element, draws hardware-accelerated 2D and 3D graphics.

The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user’s computer, using File or Blob objects to specify the file or data to read. File objects may be obtained from a FileList object returned as a result of a user selecting files using the <input> element, from a drag and drop operation’s DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement.

The Blob object represents a blob, which is a file-like object of immutable, raw data; they can be read as text or binary data, or converted into a ReadableStream so its methods can be used for processing the data. Blobs can represent data that isn’t necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user’s system.

Image compression steps

  1. Read the file using an <input> element with type=”file”
  2. Create a Blob with the file data and get its URL with createObjectURL
  3. Create an helper image object and use the blob URL as source
  4. Use the onload callback to process the image
  5. Create a canvas element setting the width and height to match the new dimensions of the image.
  6. Create a 2D context object and draw the image on the canvas
  7. Export the image with the desired output quality

    mimeType is the mime type of the result image, like image/jpeg, image/png . Value of quality ranges from 0 to 1 and represents the quality of the output image. If you don’t specify the mime and quality in the toBlob() method then default quality will be set and the mime type will be image/png. HTMLCanvasElement.toBlob is not fully supported by all browsers, see the polyfill section below.
  8. (Optional) Show the compressed image in the document

Polyfill canvas.toBlob

A low performance polyfill based on toDataURL.

Source: https://developer.mozilla.org/it/docs/Web/API/HTMLCanvasElement/toBlob

Final code

Try to upload an image to see how the resizing and compression steps works.

See the Pen
JS image resizer
by Mirco Bellagamba (@mirco-bellagamba)
on CodePen.

Mirco Bellagamba
Mirco Bellagamba

Mirco Bellagamba is a software engineer dealing with front-end web development and mobile applications. Software architectures, modern web technologies, microservices are some of the IT topics he is most interested in.

Articoli: 7

14 commenti

    • Hi Hima! Setting the value of an input type file can be tricky because of security reason. Therefore you should prefer sending compressed files via AJAX request. Anyway, if you really need this feature, I’ve found a workaround for modern browsers. The following snippet of code leverages modern DataTransfer API setting directly the files array.

      Legacy browsers like Internet Explorer and Safari < 14.1 don’t support DataTransfer constructor. The Clipboard API is involved to solve a bug in some earlier versions of Firefox.

    • If you need to download the image you could create an anchor referring to the blob URL. The code could be similar to this.

      Sending the image to the server depends on your application. If you have an API, you could send the compressed image with fetch, as multipart/form-data.

  1. Once the file is reduced, how to you actually submit the reduced file to the website? For instance, someone uploads a photo for a profile pic. In your example, someone would upload a photo to the server, where it is automatically reduced. But when I hit the Submit button, it’s still trying to upload the full sized image.

    • Assuming you are using a full-stack framework, such as Laravel or Symfony, you should replace the original image with the compressed one in the form inputs. You could do this using the code I wrote in a previous comment. For example, you could call the setFiles function like this.

  2. Great article. I wanted to achive this on vue3 but it was really hard to find some working library which such feature so I decided to do it by myself (in js). It worked perfectly. Thanks!

  3. Hi, Thanks for the example. I would like to know how one can approach if we want to restrict the size of image upto 10MB only. If we have a size of image 15MB, how we can restrict its size maximum to 10MB?

    • Hi Ishika, unfortunately the Canvas API does not natively support a maximum size parameter, so the only way to achieve your goal is through an iterative process. In the algorithm described here, you can control the image width/height and the quality factor of the canvas.toBlob method. You could try something like this:

      1. Convert the image to JPEG with an high quality factor (ex. 0.9)
      2. Check if the result size in bytes: if the size is greater than 10MB, repeat the compression with a lower quality factor.
      3. If you reach a too low quality factor (ex. 0.5), you could reduce the image width and height, reset the quality factor e repeat the previous process.

      I don’t know if this could work for you, it’s just an example of how you can iteratively control width, height and quality factor to progressively reduce the image size. Remember that this process runs in the user browser and it could be heavy in terms of performance. However, if you need this kind of feature it could be useful to consider WebAssembly solutions that rely on “native” compression algorithm (see Squoosh app).

  4. Instead of appending the canvas to document document.getElementById(“root”).append(canvas);

    can that be set as an image source so that image can be uploaded?

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.