Skip to content

Tutorial: Exportación de un canvas como imagen con Konva react (spanish version)

Pablo Marzal edited this page Aug 12, 2024 · 3 revisions

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 con onClick el botón aparecerá disabled si no hay ninguna shape en el lienzo.

handleExport: Función de manejo de la exportación

  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.

Paso 1 - Clonación y Escala: Clona el escenario original y restablece su escala.

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)

Paso 3 - Generación del Data URL

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 de 2 significa que la imagen exportada tendrá el doble de resolución.

Paso 4 - Descarga de Imagen

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);
};
  1. Crea un elemento con el URL de datos de la imagen.
  2. Establece el atributo download para definir el nombre del archivo.
  3. Agrega el enlace al body del documento, dispara el evento de clic, y elimina el enlace.

calculateCanvasBounds export-button.utils.ts

Paso 1: Definición de la interfaz

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()

Paso 2: Paso 3: Inicialización de constantes y variables

const MARGIN = 10;
const canvasBounds: CanvasBounds = {
  x: Infinity,
  y: Infinity,
  width: 0,
  height: 0,
};

Paso 3: Guarda de Canvas vacío

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.

Paso 4: Cálculo de límites

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
  }
});

Paso 5: Cálculo de width y height final y aplicación del MARGIN

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;

calculatebounds