Consigue el código actualizado aquí “comprimir imágenes en javascript” totalmente gratis para descargar.
A veces nos encontramos con la necesidad de comprimir las imágenes antes de ser subidas al servicio, ya sea por mantener una resolución inferior a la del archivo original, reducir su tamaño para que la subida sea más rápida o sobre todo como en este caso donde he tenido que plantear este código en una subida masiva de imágenes desde un formulario dinámico.
- Primero vamos a necesitar un input type file que analizaremos con Javascript para obtener el fichero que vamos a subir, en este caso una imagen.
changeFileToBase64(input.files[0]).then(data => {
//obtenemos un objeto json con el string base64
//y el fichero del input donde tenemos el tipo de fichero, el nombre
//y el tamaño real.
console.log(data)
});
//Función principal para convertir el fichero en un string base64
//con la librería FileReader() de Javascript.
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
//Función que vamos a utilizar para devolver la promesa de toBase64
// con un objeto json
async function changeFileToBase64(file) {
return {
'base64': await toBase64(file),
'file': file,
};
}
- Ahora que ya tenemos el string del fichero en base64, ya podemos comprimir la imagen configuración su resolución y calidad.
changeFileToBase64(input.files[0]).then(data => {
//Guardamos en una variable el tipo de fichero
let type = data.file.type;
//1. string base64 del fichero iriginal
//2. el tipo o mimeType del fichero "image/jpeg"
//3. Resolución basada en el Width
//4. Calidad en % desde el 0.0 como 0% al 1.0 como 100%
//Ejemplo: 1024px de width y 60% de calidad.
downscaleToImage(data.base64, type, 1024, 0.6).then(image => {
//Obtenemos el dataURL der la imagen comprimida
console.log(image)
});
});
});
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
let image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
// Configuramos parámetros por defecto
imageType = imageType || "image/jpeg";
imageArguments = imageArguments || 0.6;
// Creamos una imagen temporal en base a la original.
image = new Image();
image.src = dataUrl;
oldWidth = image.width;
oldHeight = image.height;
newHeight = Math.floor(oldHeight / oldWidth * newWidth);
// Creamos un canva para dibujar la nueva imagen.
canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
// Dibuamos la nueva imagen comprimida en el canva
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
// Obtenemos el dataURL de la nueva imagen comprimida
//y hacemos un return.
newDataUrl = canvas.toDataURL(imageType, imageArguments);
return newDataUrl;
}
- Por último ahora que ya hemos comprimido la imagen con la resolución y calidad que necesitamos, nos falta crear un nuevo fichero en base al dataURL de la nueva imagen. De esta manera podemos añadirla en un FormData() y ser subido al servidor como un fichero normal y no una cadena o string dataURL.
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 => {
//Traemos la imagen convertida en un fichero nuevo
//Adjuntamos la imagen en el append del FormData()
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});
})
);
}
Seguro que existen diferentes formas de hacer esto, pero esta es la que me ha salido a mi después de darle unas vueltas al código, podía haber elegido la opción de enviar directamente al backend un string en base64 y convertirlo en un fichero mediante PHP, guardarlo como blob en la base de datos o alguna de estás cosas que a veces se nos ocurren.
Pero en este caso concreto ya existía un tratamiento de datos en el backend mediante ficheros, y no quería alterar esas funciones en los controladores, además que por la forma en la que estaba programado y tratarse de formularios dinámicos, su tratamiento en el backend tenia que mantenerse tal y como estaba.
¿Por qué se ha tenido que hacer de esta manera en este caso?
Pues bien el principal problema que teníamos con este script, se trataba a la hora de tratar los formularios dinámicos y los input de carga de ficheros (en este caso solo imágenes) y es que cuando se enviaba el formulario mediante ajax al tratar con una cantidad considerable de imágenes o de gran tamaño, debido a la configuración del servidor pasado 10 segundos cortaba la conexión, y si te estás preguntando el porque no hemos tocado esta configuración nada más empezar es porque no queríamos alargar los tiempos de espera ya que se debe mantener un rendimiento estable en el servidor web.
Por eso se ha elegido por tratar 2 puntos importaciones: no se necesitaban imágenes de gran tamaño ni tampoco imágenes con una resolución grande. Por eso he preferido comprimirlas directamente al mismo tiempo que se enviaba el formulario mediante fetch.
De esta manera se han conseguido subidas mucho más rápidas mejorando los tiempos de carga y respuestas por parte del servidor sin disminuir el rendimiento ni entorpecer la experiencia del usuario mientras sube las imágenes.
3 respuestas
Excelente trabajo, muy bien hecho. pregunto se podria hacer procesando la toma de una foto desde celular, o una webcam, reducirle y enviarla al servidor?
¡Hola, Óscar! Me alegra que te haya gustado el trabajo. Sobre tu pregunta, sí, es completamente posible capturar una foto desde un dispositivo móvil o una webcam, reducir su tamaño y luego enviarla al servidor utilizando un enfoque similar al que he compartido en el código.
Aquí te explico cómo podrías hacerlo:
1. Capturar la imagen desde la cámara o webcam:
Puedes utilizar la API getUserMedia para acceder a la cámara del dispositivo y capturar una imagen. Luego, puedes convertir esa imagen en un archivo y procesarla como lo haces con una imagen subida desde un input de tipo file.
// Accede a la cámara y captura una imagen
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
const video = document.createElement('video');
video.srcObject = stream;
video.play();
// Después de un tiempo, captura la imagen del video
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);
// Convertir el canvas a dataURL
const imageDataURL = canvas.toDataURL('image/jpeg', 1.0);
// Aquí puedes aplicar la compresión como en el ejemplo anterior
downscaleImage(imageDataURL, 1024, 'image/jpeg', 0.6)
.then(compressedImageDataURL => {
urltoFile(compressedImageDataURL, 'captured-image.jpg', 'image/jpeg')
.then(file => {
// Envía el archivo comprimido al servidor
const formData = new FormData();
formData.append('image', file);
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
console.log('Imagen enviada con éxito', response);
}).catch(error => {
console.error('Error al enviar la imagen', error);
});
});
});
// Detenemos el uso de la cámara
stream.getTracks().forEach(track => track.stop());
}, 3000); // Captura la imagen después de 3 segundos
})
.catch(error => {
console.error('Error al acceder a la cámara', error);
});
2. Reducir el tamaño y enviar al servidor:
El proceso de reducción y envío es exactamente el mismo que en tu código original. Una vez que tienes el dataURL de la imagen capturada, puedes aplicar la función downscaleImage para reducir la resolución y calidad, y luego convertirla en un archivo con urltoFile para enviarla al servidor.
Consideraciones:
Permisos: Asegúrate de que el usuario haya dado permiso para acceder a la cámara.
Tiempos de captura: Puedes ajustar el tiempo de captura de la imagen según tus necesidades.
Compatibilidad: La API getUserMedia es compatible con la mayoría de los navegadores modernos, pero siempre es bueno hacer pruebas de compatibilidad.
Espero que esto te haya dado una idea clara de cómo podrías adaptar este enfoque para capturar y procesar imágenes desde una cámara o webcam. ¡Si tienes más preguntas, no dudes en preguntar!
Muchas gracias por tu aporte fue muy valioso para mi, se agradece. saludos cordiales.