Compress images in the browser

Uploading a profile image, you have surely already experienced this situation. This little image that will do at best 250px * 250px. Put yourself in the skin of granny of the cantal, do you think it will resize its small image before upload? Redimensio-what? Ok, our good user who scales an image of 8000px * 6000px will have to wait a long time to send all this before, finally, the server optimizes the latter.

But why not do all this directly in the browser ?! We win in every way. The user does not lose his time to upload xMb for nothing, you save server power, you will not frustrate who knows (but who has the lazy to open his Photoshop), and then you work for the planet! 5 good reasons to do this in the browser. Let’s go ?

It is by programming the interface of preferences of Buzeo that I thought about it. The small profile image makes 60px * 60px, so put it directly in the right format.

preferences buzeo
Buzeo Preferences Panel

How it works ?

Every modern browser has everything you need. Here is how we will proceed:

  1. an image is retrieved directly from one <input type="file">,
  2. the image is converted HTMLImageElementso that it can be used withcanvas
  3. we can “display” the image in the canvas to the desired dimensions (the canvas will not even be inserted in the DOM, so invisible),
  4. we return the image to base64 which can be directly supplied as srca tag imgand sent to the server.

Nothing extremely complex. I simply omitted from the list above a little technical detail. Resizing an image involves keeping its ratio if you do not want it to deform. We will therefore go through an intermediate step in order to obtain the real dimensions of the image in order to calculate the ratio.

Let’s code!

For clarity, we will organize the code into different functions. This also allows us to cut the process into logical steps.

Convert image to base64

Let’s start by creating the function to convert the image to base64.

function convertFileToB64 (file, callback) {convertFileToB64 ( file , callback ) { 
    // create an object fileReader. // create an object fileReader. 
    // This allows you to read the contents of the files// This allows you to read the contents of the files
    const reader = new FileReader ();const reader = new FileReader ();  

    // as soon as the file is loaded, we send the result// as soon as the file is loaded, we send the result
    reader.addEventListener ('load', () => {. addEventListener ( 'load' , () => {   
        callback (reader.result);( Reader . Result );
    }, false);}, false ); 

    // we specify that we want to file in the form of a dataURL (base64)// we specify that we want to file in the form of a dataURL (base64)
    reader.readAsDataURL (file);. readAsDataURL ( file );
}}

This function takes as input a file type file, as retrieved from a file selection field. As in the example below.

// we imagine that our field has the id = "upload-image"
const inputFile = document.getElementById ('upload-image');const inputFile = document . getElementById ( 'upload-image' );

// it is necessary to listen to the changes to know when a file is selected// it is necessary to listen to the changes to know when a file is selected
inputFile.onchange = function (event) {. onchange = function ( event ) {  
   const img = inputFile.files [0];const img = inputFile . files [ 0 ];
}}

Obtain a workable image object

In a second step, the function to obtain the resolution of the image in pixels is created. Moreover, canvasdoes not include the base64, so we will feed him HTMLImageElement.

function getImage (b64img, callback) {getImage ( b64img , callback ) { 
    // create an empty HTMLImageElement// create an empty HTMLImageElement
    const imgObj = new Image ();const imgObj = new Image ();  

    // as soon as the image is loaded, callback// as soon as the image is loaded, callback
    imgObj.onload = () => {. onload = () => {   
        callback (imgObj);( imgObj );
    };};

    // it is given the source image// it is given the source image
    imgObj.src = b64img;. src = b64img ;
}}

This function takes as input an image encoded in base64, it falls well, we already created the function that allows us to do this! It returns in the callback an object of type HTMLImageElementfrom which we can access the properties of the image.

resizing

We now have what we need to attack the resizing, we go to serious things! History to have a function a little flexible, we will propose two output formats: jpeg and png. It will also be possible to force the dimensions, ie the original ratio will be ignored and the image will be resized precisely to the values ​​given in parameters.

Our function will accept the following parameters:

  • img: it is an image object as retrieved from the field <input type="file">
  • options:
    • {string} outputFormat – (jpe? g or png)
    • {string} targetWidth – desired width
    • {string} targetHeight – desired height
    • {bool} forceRatio – if true, force dimensions even if it distorts the image
  • callback
function resize (img, options, callback) {resize ( img , options , callback ) { 
    // make sure the image is in jpeg or png// make sure the image is in jpeg or png
    // here we use a REGEX for more conciseness// here we use a REGEX for more conciseness
    if (! / (jpe? g | png) $ / i.test (img.type)) {if (! / (g jpe |? png) $ / i . test ( img . Type )) {  
        callback (new TypeError ('image must be either jpeg or png'));( new TypeError ( 'image must be either jpeg or png' )); 
        return;return ;
    }}

    // check that the output format is supported (jpeg, jpg or png)// check that the output format is supported (jpeg, jpg or png)
    if (options.outputFormat! == 'jpg' && options.outputFormat! == 'jpeg' && options.outputFormat! == 'png') {if ( options . outputFormat ! == 'jpg' && options . outputFormat ! == 'jpeg' && options . outputFormat ! ==       'PNG' ) { 
        callback (new Error ('outputFormat must be jpeg or png'));( new  Error ( 'outputFormat must be Either jpe g or png?' ));
        return;return ;
    }}

    // define the format// define the format
    const output = (options.outputFormat === 'png')? 'png': 'jpeg';const output = ( options . outputFormat === 'png' ) ? 'png' : 'jpeg' ;      

    // we call our small practical functions;)// we call our small practical functions;)
    convertFileToB64 (img, (b64img) => {( img , ( b64img ) =>    {
        getImage (b64img, (imgObj) => {( b64img , ( imgObj ) =>    {
            // prepares the canvas and retrieves the dimensions of the image// prepares the canvas and retrieves the dimensions of the image
            const canvas = document.createElement ('canvas');const canvas = document . createElement ( 'canvas' );
            const context = canvas.getContext ('2d');const context = canvas . getContext ( '2d' );
            const imgWidth = imgObj.width;const imgWidth = imgObj . width ;
            const imgHeight = imgObj.height;const imgHeight = imgObj . height ;
            let width;let width ;
            let height;let height ;

            // calculation of the ratio// calculation of the ratio
            // it is assumed that the image should not exceed the dimensions provided// it is assumed that the image should not exceed the dimensions provided
            // so if the length and the height do not correspond to the ratio// so if the length and the height do not correspond to the ratio
            // adjust the image to maintain the ratio while not exceeding the width or height // adjust the image to maintain the ratio while not exceeding the width or height 
            if (! options.forceRatio) {if (! options . forceRatio ) {  
                if (imgWidth> imgHeight) {if ( imgWidth > imgHeight )   {
                    width = options.targetWidth;= options . targetWidth ;
                    height = Math.round ((imgHeight / imgWidth) * width);= Math . round (( imgHeight / imgWidth ) * width   );
                }}

                else {else  {
                    height = options.targetHeight;= options . targetHeight ;
                    width = Math.round ((imgWidth / imgHeight) * height);= Math . round (( imgWidth / imgHeight ) * height   );
                }}
            }}

            // no calculation of the ratio if the forceRatio option is true// no calculation of the ratio if the forceRatio option is true
            else {else  {
                width = options.targetWidth;= options . targetWidth ;
                height = options.targetHeight;= options . targetHeight ;
            }}

            // we give its dimensions to the canvas// we give its dimensions to the canvas
            canvas.width = width;. width = width ;
            canvas.height = height;. height = height ;

            // draw the image// draw the image
            context.drawImage (imgObj, 0, 0, width, height);. drawImage ( imgObj , 0 , 0 , width , height );  

            // export the image to the format you want in base64// export the image to the format you want in base64
            // directly in the callback// directly in the callback
            callback (null, canvas.toDataURL (`image / $ {output}`));( null , canvas . toDataURL ( `image / $ {output}` ));
        });});
    });});
}}

And here you see that a seemingly complex task finally takes place in three functions and a few hundred lines of code. I believe that the power of APIs js browsers is no longer to be demonstrated.

To do this properly, it is best to encapsulate this code in a module to avoid polluting the global space with unnecessary functions (they only serve as part of resize).

You will find the complete code on Github with all the information to install it. With bower, it’s even as simple as:

bower install resizeimage --save- save

In addition, in the Github version, I added the possibility of cropping intelligently. For example, if the destination format is square but the source photo is in portrait (or landscape) format, the image will be automatically cropped on the face!

Your visitors already feel better and thank you!

Note that if you need to support older browsers, you should set up some tests to make sure that canvas,Image()and Reader()are supported. Also note that this code is written in ES6, not all browsers understand it. Remember to transpile the code into ES5 if necessary. If you have nothing in place to automate this process, you can do it manually online .

Written by
With a natural and eclectic curiosity, internet surfer, I am interested in everything related to digital: from programming to entrepreneurship, via webmarketing and social networks.

Have your say!

0 0

Lost Password

Please enter your username or email address. You will receive a link to create a new password via email.