Integrar recorte y optimización a WebP en las subidas de imagen del panel
Los tres uploaders del panel (logo, "quiénes somos" y galería) ahora abren un recorte interactivo (zoom + encuadre) y reescalan en el navegador antes de subir: logo y "quiénes somos" a máx. 500 px, galería a máx. 1200 px, reencodando a WebP (calidad 0.82). El SVG del logo se sube tal cual para no rasterizar el vector. Esto reduce el peso de los data URIs base64 que se guardan en Postgres y se inlinean en el funnel. Nueva dependencia react-easy-crop: librería de recorte ligera, sin estado global, compatible con React 19; el reescalado y reencodado se hacen con canvas nativo (lib/image/crop.ts), sin dependencias extra.
This commit is contained in:
38
mvp/b2c/src/lib/image/crop.ts
Normal file
38
mvp/b2c/src/lib/image/crop.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export type CropArea = { x: number; y: number; width: number; height: number };
|
||||
|
||||
// Recorta la región indicada (en píxeles del original) y reescala el lado más
|
||||
// largo a `maxDimension`, devolviendo un WebP ya optimizado listo para subir.
|
||||
export async function getCroppedWebp(
|
||||
src: string,
|
||||
area: CropArea,
|
||||
maxDimension: number,
|
||||
quality = 0.82,
|
||||
): Promise<Blob> {
|
||||
const img = await loadImage(src);
|
||||
const scale = Math.min(1, maxDimension / Math.max(area.width, area.height));
|
||||
const outW = Math.max(1, Math.round(area.width * scale));
|
||||
const outH = Math.max(1, Math.round(area.height * scale));
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = outW;
|
||||
canvas.height = outH;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('No se pudo crear el lienzo.');
|
||||
ctx.imageSmoothingQuality = 'high';
|
||||
ctx.drawImage(img, area.x, area.y, area.width, area.height, 0, 0, outW, outH);
|
||||
|
||||
const blob = await new Promise<Blob | null>((resolve) =>
|
||||
canvas.toBlob(resolve, 'image/webp', quality),
|
||||
);
|
||||
if (!blob) throw new Error('No se pudo procesar la imagen.');
|
||||
return blob;
|
||||
}
|
||||
|
||||
function loadImage(src: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = () => reject(new Error('No se pudo cargar la imagen.'));
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user