How to Compress an Image Via JavaScript in the Browser

How to compress an image via Javascript in the browser?

In short:

  • Read the files using the HTML5 FileReader API with .readAsArrayBuffer
  • Create a Blob with the file data and get its url with window.URL.createObjectURL(blob)
  • Create new Image element and set it's src to the file blob url
  • Send the image to the canvas. The canvas size is set to desired output size
  • Get the scaled-down data back from canvas via canvas.toDataURL("image/jpeg",0.7) (set your own output format and quality)
  • Attach new hidden inputs to the original form and transfer the dataURI images basically as normal text
  • On backend, read the dataURI, decode from Base64, and save it

Source: code.

Image Compression when fetching from remote server

The approach of this kind of problem usually involves lazy loading of images.
The approach could be:

  • Load only the images that are in the frame of your view, wait to load the next images until scroll reaches them
  • Load image on demand (on click, hover and so on)

Although you can compress images client side, this will not avoid to download them from the server (100 img = 100MB in your case)

There are a lot of npm packages that lazy load components (and thus images) for you, like this

Image compressing/resizing very slow

Your resize function really is badly named. What it does is to change the quality of the jpeg lossy algorithm. It doesn't really resize your images, and I guess your 8MB image is quite large and if it comes from the device camera, the compression might already be quite decent.

So when you draw it on the canvas, you are actually producing a new raw image (without any compression), with the same amount of pixels than in your original file.

This means that your canvas itself will be way larger than the original 8MB in memory. When calling toDataURL, it will have to extract this data, process it etc, eating even more memory.

And it's not even sure that at the end you'll get a lighter file...

If you're ok to really resize (i.e change the dimensions of) your images, then it will be easier for your device to handle it :

function resize(sourceImgObj) {    const mimeType = "image/jpeg"; // would probably be better to keep the one of the file...    let quality = .92; // default;    const cvs = document.createElement('canvas');    const MAX_SIZE = 500; // in px for both height and width    const w = sourceImgObj.naturalWidth;    const h = sourceImgObj.naturalHeight;    let ratio = MAX_SIZE / Math.max(w, h);    if(ratio > 1){ // if it's smaller than our defined MAX_SIZE      ratio = 1;      quality = .5; // lower the jpeg quality      }    cvs.width = ratio * w;    cvs.height = ratio * h;    let ctx = cvs.getContext("2d").drawImage(sourceImgObj, 0, 0, cvs.width, cvs.height);
// note : if it's not a problem to convert it to async then toBlob would be a better choice let newImageData = cvs.toDataURL(mimeType, quality); return newImageData;}
inp.onchange = e => { const MAX_SIZE = 100000; // 100KB let img = new Image(); img.src = URL.createObjectURL(inp.files[0]); img.onload = e => { // if it's not too big, return the image's current src let dataURL = (inp.files[0].size > MAX_SIZE) ? resize(img) : img.src; let out = new Image(); out.src = dataURL; document.body.appendChild(out); }; }
<input type="file" id="inp">

Reading image into JS to subsequently compress it

In my mind (but it's a bit subjective), you would do it in both places.

  1. User selects a File from your input
  2. You process the file through js
  3. If your processing failed (e.g the file was not an image / corrupted / who knows) you can let the user know directly.
  4. If the processing succeeded, when user clicks submit, you overwrite the default behavior of your form, and send a FormData containing your new File/Blob instead of the original one.

var toSend = null, // here we will store our processed File/Blob (the one to send)browse_image = document.getElementById('browse_image');
// when user selects a new Filebrowse_image.onchange = process_user_file;// when user decides to send it to serverdocument.querySelector('form').onsubmit = overwrite_default_submit;
// grab the file from the input and process itfunction process_user_file(evt) { // since we attached the event listener through elem.onevent, // 'this' refers to the input var file = this.files[0]; // here do your compression, for demo, we'll just check it's a png file var reader = new FileReader(); // a FileReader is async, so we pass the actual checking script as the onload handler reader.onload = checkMagicNumber; reader.readAsArrayBuffer(file.slice(0,4));}
// we don't want the real form to be submitted, but our processed File/Blobfunction overwrite_default_submit(evt) { // block the form's submit default behavior evt.preventDefault(); // create a new form result from scratch var form = new FormData(); // add our File/Blob form.append("myfile", toSend, browse_image.files[0].name); // create a new AJAX request that will do the same as original form's behavior var xhr = new XMLHttpRequest(); xhr.open('POST', evt.target.action);// xhr.send(form); // uncomment to really send the request console.log('sent', toSend);}
// simply checks if it's really a png file// for you, it will be your own compression code,// which implementation can not be discussed in this answerfunction checkMagicNumber(evt) { var PNG = '89504e47'; var arr = new Uint8Array(evt.target.result); var header = ""; for(var i = 0; i < arr.length; i++) { header += arr[i].toString(16); } // some user friendly actions if(header !== PNG) { alert('not a png file'); // let the user know it didn't work browse_image.value = ""; // remove invalid File sub.disabled = true; // avoid the form's submission toSend = null; // nothing to send } else { toSend = browse_image.files[0]; // for demo we don't actually modify it... sub.disabled = false; // allow form's submission }}
<form method="POST" action="some_url">  <label>Please select a .png file</label>  <input type="file" id="browse_image" name="myfile">  <input type="submit" value="Submit" disabled id="sub"></form>


Related Topics



Leave a reply



Submit