-
Notifications
You must be signed in to change notification settings - Fork 18
Tutorial: Exportación de un canvas como imagen con Konva react (spanish version)
El objetivo es permitir que los usuarios exporten sus creaciones. Usando la biblioteca Konva
, podemos manejar gráficos en canvas. Este tutorial guiará a través del proceso de crear un botón de exportación en React que captura y descarga el estado actual del canvas.
ExportButton: React Component
export const ExportButton = () => {
const { stageRef, shapes } = useCanvasContext();
return (
<ToolbarButton
onClick={handleExport}
className={classes.button}
disabled={shapes.length === 0}
>
<ExportIcon />
<span>Export</span>
</ToolbarButton>
);
};
-
useCanvasContext
: Extraemos el stageRef (referencia al escenario de Konva) y shapes (formas dibujadas) del contexto. -
<ToolbarButton/>
: Componente que representa un botón en la barra de herramientas e inicia la descarga cononClick
el botón aparecerádisabled
si no hay ninguna shape en el lienzo.
const handleExport = () => {
if (stageRef.current) {
const originalStage = stageRef.current;
const clonedStage = originalStage.clone();
resetScale(clonedStage);
const bounds = calculateCanvasBounds(shapes);
const dataURL = clonedStage.toDataURL({
mimeType: 'image/png', // Change to jpeg to download as jpeg
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
pixelRatio: 2,
});
createDownloadLink(dataURL);
}
};
Condición Inicial: Comprueba si stageRef.current es diferente de null
.
El motivo principal de trabajar con un clon del stage original es no modificar la escala ni el zoom impuesta por el usuario a la hora de realizar el export. Además, si trabajamos con el canvas original nos encontramos con la problemática de tener que ocultar y mostrar los transformers para que no aparezcan en la imagen, de esta manera nos olvidamos de ello.
Podemos restablecer la escala del stage clonado sin miedo ya que se crea en el momento de cada exportación, para ello utilizamos la función:
const resetScale = (stage: Stage) => {
stage.scale({ x: 1, y: 1 });
};
Paso 2 - calculateCanvasBounds
: Cálculo para determinar el área a exportar basado en las shapes dibujadas (seguir leyendo)
La función toDataURL se utiliza para exportar una imagen del canvas con configuraciones personalizables que definen el tipo de imagen y el área a capturar, lo utilizaremos sobre el stage clonado. Konva Doc
const dataURL = clonedStage.toDataURL({
mimeType: 'image/png', // Change to jpeg to download as jpeg
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
pixelRatio: 2,
});
-
mimeType
: Especifica el tipo de imagen a generar. Puedes cambiarlo a 'image/jpeg' para obtener un JPEG. -
x
: Esta coordenada determina desde dónde comenzará a capturarse la imagen en el eje horizontal. -
y
: Esta coordenada determina desde dónde comenzará a capturarse la imagen en el eje vertical. -
width
: Define la extensión horizontal del área a exportar. -
height
: Define la extensión vertical del área a exportar. -
pixelRatio
: Define la relación de píxeles para la exportación. Un valor de2
significa que la imagen exportada tendrá el doble de resolución.
createDownloadLink
: Genera un enlace de descarga con la Data URL de la imagen PNG que hemos definido anteriormente.
const createDownloadLink = (dataURL: string) => {
const link = document.createElement('a');
link.href = dataURL;
link.download = 'canvas.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
- Crea un elemento con el URL de datos de la imagen.
- Establece el atributo download para definir el nombre del archivo.
- Agrega el enlace al body del documento, dispara el evento de clic, y elimina el enlace.
calculateCanvasBounds export-button.utils.ts
export interface CanvasBounds {
x: number;
y: number;
width: number;
height: number;
}
Definimos el tipo de objeto a retornar, son los 4 párametros que necesitamos en la función toDataURL()
const MARGIN = 10;
const canvasBounds: CanvasBounds = {
x: Infinity,
y: Infinity,
width: 0,
height: 0,
};
if (shapes.length === 0) {
return {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
Este caso nunca debería darse ya que el botón debería aparecer disabled
.
Utilizaremos como ejemplo la siguiente figura: {x: 50, y: 50, width: 100, height: 20}
y un MARGIN:10
shapes.forEach(shape => {
// Calculate min x and y
if (shape.x < canvasBounds.x) {
canvasBounds.x = shape.x; // 50
}
if (shape.y < canvasBounds.y) {
canvasBounds.y = shape.y; // 50
}
// Calculate max x and y
if (shape.x + shape.width > canvasBounds.width) {
canvasBounds.width = shape.x + shape.width; // 150
}
if (shape.y + shape.height > canvasBounds.height) {
canvasBounds.height = shape.y + shape.height; // 70
}
});
canvasBounds.x -= MARGIN; // 50 - 10 = 40
canvasBounds.y -= MARGIN; // 50 - 10 = 40
canvasBounds.width = canvasBounds.width - canvasBounds.x + MARGIN; // 150 - 40 + 10 = 120
canvasBounds.height = canvasBounds.height - canvasBounds.y + MARGIN; // 70 - 40 + 10 = 40
return canvasBounds;