You might be working on a project which has an image upload feature that takes images from the user and uploads it to your storage server. Once you have implemented it then you start thinking of optimizing it, so different factors like the format, quality, resolution, size of the image etc… come into consideration.
Later you decide to compress the images to save your storage space, so you implement an image compression feature in the back-end. Now you have saved your storage space. Don’t stop there because you can optimize more, save more resources like bandwidth and CPU cycles. If you have a server with limited resources and has many tasks to run then you are just adding more CPU load.
What if you can save your storage space, bandwidth and reduce server load at the same time. Yes, it is possible, the answer is“Compression at the client side using JavaScript”. Now let’s implement it.
Take advantage of the HTML5 Canvas that is used to draw graphics on a web page. Canvas is just a container for your graphics, JavaScript is used to draw.
Steps
Create an instance of JavaScript FileReader API.
const reader = new FileReader();
Read the input image using FileReader.
reader.readAsDataURL(sourceImage);
Create an instance of Image.
const img = new Image();
Set the result of the FileReader as source for the image.
img.src = event.target.result;
Create a HTML5 Canvas element
const elem = document.createElement(‘canvas’);
Set the width and height of the canvas to match the new dimensions of the image.
elem.width = width;
elem.height = height;
Create an object that is used to draw graphics on the canvas.
const ctx = elem.getContext(‘2d’)
The getContext() method returns an object with the properties and methods required for drawing graphics on the canvas. The‘2d‘parameter limits us for drawing only 2D graphics.
Now draw the image on the canvas by specifying the position, width and height of the image.
ctx.drawImage(img, 0, 0, width, height);Export the canvas as a blob or DataURL by specifying MIME type, image quality.
const data = ctx.canvas.toDataURL(img, mime, quality);
or
ctx.canvas.toBlob((blob) => {
console.log(blob); //output image as a blob
const file = new File([blob], fileName, {
type: mime,
lastModified: Date.now()
}); //output image as a file
}, mime, quality);
mime is the“mime type”of the image, like‘image/jpeg’,‘image/png’.
Value of quality ranges from 0 to 1. It is 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’.
The final code
/*
<!– HTML Part –>
<input id=”file” type=”file” accept=”image/*”>
<script>
document.getElementById(“file”).addEventListener(“change”, function (event) {
compress(event);
});
</script>
*/
compress(e) {
const width = 500;
const height = 300;
const fileName = e.target.files[0].name;
const reader = new FileReader();
reader.readAsDataURL(e.target.files[0]);
reader.onload = event => {
const img = new Image();
img.src = event.target.result;
img.onload = () => {
const elem = document.createElement(‘canvas’);
elem.width = width;
elem.height = height;
const ctx = elem.getContext(‘2d’);
// img.width and img.height will contain the original dimensions
ctx.drawImage(img, 0, 0, width, height);
ctx.canvas.toBlob((blob) => {
const file = new File([blob], fileName, {
type: ‘image/jpeg’,
lastModified: Date.now()
});
}, ‘image/jpeg’, 1);
},
reader.onerror = error => console.log(error);
};
}
Note:
If you want to maintain the aspect ratio of the output image then you can set either the width or height as constant and calculate the other dimension.
const width = 600;
const scaleFactor = width / img.width;
elem.width = width;
elem.height = img.height * scaleFactor;
ctx.drawImage(img, 0, 0, width, img.height * scaleFactor);
Here we kept width as constant and calculated the scaling factor. To find the relative height just multiply the scaling factor to the original height.
For browsers that don’t support“toBlob”method
Use this polyfill“https://developer.mozilla.org…”.
Modify the toBlob parameters as shown otherwise you will get“function expected”error.
//toBlob polyfill
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, ‘toBlob’, {
value: function (callback, type, quality) {
var dataURL = this.toDataURL(type, quality).split(‘,’)[1];
setTimeout(function() {
var binStr = atob(dataURL),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob( [arr], {type: type || ‘image/png’} ) );
});
}
});
}
// toBlob usage
ctx.canvas.toBlob(function (blob) {
console.log(blob); //access blob here
}, mimeType, quality);