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:
- The file upload is faster if the image is smaller, which means the user can move on to other things faster.
- The upload will not only be faster but it will also take up less bandwidth. This is nice for the user, as it might save their data plan, but also nice for server bandwidth costs.
- The server doesn't have to do image processing as images have already been processed, which saves CPU cycles but also dependencies which can introduce security risks.
- The image takes up less space on the server (but admittedly this would also be a benefit when compressing on the server)
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.