Get the updated code here «compress images in javascript» totally free to download.
Sometimes we find the need to compress the images before being uploaded to the service, either to maintain a lower resolution than the original file, reduce its size so that the upload is faster or, above all, as in this case where I have Had to raise this code in a massive upload of images from a dynamic form.
- First we are going to need an input type file that we will analyze with Javascript to obtain the file that we are going to upload, in this case an image.
changeFileToBase64(input.files[0]).then(data => { //we obtain a json object with the base64 string //and the input file where we have the type of file, the name //and the actual size. console .log(data) }); //Main function to convert the file into a base64 string //with the Javascript FileReader() library. const toBase64 = file => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); //Function that we are going to use to return the promise of toBase64 // with a json object async function changeFileToBase64(file) { return { 'base64': await toBase64(file), 'file': file, }; }
- Now that we have the file string in base64, we can compress the image by setting its resolution and quality.
changeFileToBase64(input.files[0]).then(data => { //We save the file type in a variable let type = data.file.type; //1. base64 string of the original file //2. the type or mimeType of the "image/jpeg" file //3. Resolution based on the Width //4. Quality in % from 0.0 as 0% to 1.0 as 100% //Example: 1024px width and 60% quality. base64, type, 1024, 0.6).then(image => { //We get the dataURL of the compressed image console.log(image) }); }); function downscaleImage(dataUrl, newWidth, imageType, imageArguments) { let image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl; // We configure default parameters imageType = imageType || "image/jpeg"; imageArguments = imageArguments || 0.6; // We create a temporary image based on the original. image = new Image(); image.src = dataUrl; oldWidth = image.width; oldHeight = image.height; newHeight = Math.floor(oldHeight / oldWidth * newWidth); // We create a canvas to draw the new image. canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; // We draw the new compressed image on the canvas ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, newWidth, newHeight); // We get the dataURL of the new compressed image //and make a return. newDataUrl = canvas.toDataURL(imageType, imageArguments); return newDataUrl; }
- Finally, now that we have compressed the image with the resolution and quality we need, we need to create a new file based on the dataURL of the new image. This way we can add it to a FormData() and it can be uploaded to the server as a normal file and not a string or dataURL string.
changeFileToBase64(input.files[0]).then(data => { let type = data.file.type; downscaleToImage(data.base64, type, 1024, 0.6).then(image => { urltoFile(image, data. file.name, data.file.type) .then(file => { //We bring the converted image into a new file //We attach the image to the FormData() append formData.append(input.id, file); //console.log(file); }); function urltoFile(url, filename, mimeType) { return (fetch(url) .then(function (res) { return res.arrayBuffer(); }) .then(function (buf) { return new File([buf], filename , {type: mimeType}); }
Surely there are different ways to do this, but this is the one that came out to me after giving the code a few turns, I could have chosen the option of sending a string in base64 directly to the backend and converting it into a file using PHP, saving it like a blob in the database or one of these things that sometimes occur to us.
But in this specific case there was already data processing in the backend using files, and I did not want to alter those functions in the controllers, in addition to the way it was programmed and being dynamic forms, its treatment in the backend had to stay as it was.
Why did it have to be done this way in this case?
Well, the main problem we had with this script was when dealing with dynamic forms and file upload inputs (in this case only images) and that is that when the form was sent using ajax when dealing with a quantity considerable number of images or large sizes, due to the server configuration, after 10 seconds the connection was cut, and if you are wondering why we have not touched this configuration right away, it is because we did not want to lengthen the waiting times since it must be maintained stable performance on the web server.
That is why it was chosen to address 2 import points: large images were not needed nor images with a large resolution. That's why I preferred to compress them directly at the same time as sending the form via fetch.
In this way, much faster uploads have been achieved, improving loading times and responses from the server without reducing performance or hindering the user experience while uploading the images.
3 responses
Excellent work, very well done. I wonder if it could be done by processing a photo taken from a cell phone or a webcam, reducing it and sending it to the server?
Hi Oscar! I'm glad you liked the work. Regarding your question, yes, it is entirely possible to capture a photo from a mobile device or webcam, reduce its size and then send it to the server using a similar approach to the one I've shared in the code.
Here I explain how you could do it:
1. Capture the image from the camera or webcam:
You can use the getUserMedia API to access the device's camera and capture an image. You can then convert that image to a file and process it just like you would an image uploaded from a file input.
// Access the camera and capture an image
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
const video = document.createElement('video');
video.srcObject = stream;
video.play();
// After a while, capture the video image
setTimeout(() => {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, canvas.width, canvas.height);
// Convert the canvas to dataURL
const imageDataURL = canvas.toDataURL('image/jpeg', 1.0);
// Here you can apply compression as in the previous example
downscaleImage(imageDataURL, 1024, 'image/jpeg', 0.6)
.then(compressedImageDataURL => {
urltoFile(compressedImageDataURL, 'captured-image.jpg', 'image/jpeg')
.then(file => {
// Send the compressed file to the server
const formData = new FormData();
formData.append('image', file);
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
console.log('Image sent successfully', response);
}).catch(error => {
console.error('Error sending image', error);
});
});
});
// We stop using the camera
stream.getTracks().forEach(track => track.stop());
}, 3000); // Capture the image after 3 seconds
})
.catch(error => {
console.error('Error accessing camera', error);
});
2. Reduce size and send to server:
The downscaling and sending process is exactly the same as in your original code. Once you have the dataURL of the captured image, you can apply the downscaleImage function to reduce the resolution and quality, and then convert it to a file with urltoFile to send it to the server.
Considerations:
Permissions: Make sure the user has given permission to access the camera.
Capture times: You can adjust the image capture time according to your needs.
Compatibility: The getUserMedia API is supported by most modern browsers, but it is always a good idea to test for compatibility.
I hope this has given you a clear idea of how you could adapt this approach to capture and process images from a camera or webcam. If you have any further questions, please feel free to ask!
Thank you very much for your contribution, it was very valuable to me, I appreciate it. Best regards.