OffscreenCanvas: Acelera tus operaciones de lienzo con un trabajador web

Tim Dresser

Canvas es una forma popular de dibujar todo tipo de gráficos en la pantalla y un punto de entrada al mundo de WebGL. Se puede usar para dibujar formas, imágenes, ejecutar animaciones o incluso mostrar y procesar contenido de video. A menudo, se usa para crear experiencias del usuario atractivas en aplicaciones web y juegos en línea con mucho contenido multimedia.

Se puede crear una secuencia de comandos, lo que significa que el contenido dibujado en el lienzo se puede crear de manera programática, por ejemplo, en JavaScript. Esto le brinda al lienzo una gran flexibilidad.

Al mismo tiempo, en los sitios web modernos, la ejecución de secuencias de comandos es una de las fuentes más frecuentes de problemas de capacidad de respuesta del usuario. Debido a que la lógica y la renderización del lienzo se producen en el mismo subproceso que la interacción del usuario, los cálculos (a veces pesados) que se involucran en las animaciones pueden perjudicar el rendimiento real y percibido de la app.

Afortunadamente, OffscreenCanvas es una respuesta a esa amenaza.

Navegadores compatibles

  • Chrome: 69.
  • Edge: 79.
  • Firefox: 105.
  • Safari: 16.4.

Origen

Anteriormente, las capacidades de dibujo en lienzo estaban vinculadas al elemento <canvas>, lo que significaba que dependían directamente del DOM. Como su nombre lo indica, OffscreenCanvas desacopla el DOM y la API de Canvas moviéndolos fuera de la pantalla.

Gracias a esta desvinculación, la renderización de OffscreenCanvas se desconecta por completo del DOM y, por lo tanto, ofrece algunas mejoras de velocidad en comparación con el lienzo normal, ya que no hay sincronización entre ambos.

Además, se puede usar en un trabajador web, aunque no haya un DOM disponible. Esto permite todo tipo de casos de uso interesantes.

Usa OffscreenCanvas en un trabajador

Los trabajadores son la versión de subprocesos de la Web, que te permiten ejecutar tareas en segundo plano.

Mover algunas de tus secuencias de comandos a un trabajador le brinda a tu app más margen para realizar tareas críticas para el usuario en el subproceso principal. Sin OffscreenCanvas, no había forma de usar la API de Canvas en un trabajador, ya que no había un DOM disponible.

OffscreenCanvas no depende del DOM, por lo que se puede usar. En el siguiente ejemplo, se usa OffscreenCanvas para calcular un color de gradiente en un trabajador:

// file: worker.js
function getGradientColor(percent) {
 
const canvas = new OffscreenCanvas(100, 1);
 
const ctx = canvas.getContext('2d');
 
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
  gradient
.addColorStop(0, 'red');
  gradient
.addColorStop(1, 'blue');
  ctx
.fillStyle = gradient;
  ctx
.fillRect(0, 0, ctx.canvas.width, 1);
 
const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
 
const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
 
return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}

getGradientColor
(40);  // rgba(152, 0, 104, 255 )

Cómo desbloquear el subproceso principal

Mover el cálculo intensivo a un trabajador te permite liberar recursos significativos en el subproceso principal. Usa el método transferControlToOffscreen para duplicar el lienzo normal en una instancia de OffscreenCanvas. Las operaciones aplicadas a OffscreenCanvas se renderizarán en el lienzo de origen automáticamente.

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker
.postMessage({canvas: offscreen}, [offscreen]);

En el siguiente ejemplo, el cálculo intensivo ocurre cuando cambia el tema de color; debería tardar unos pocos milisegundos, incluso en una computadora de escritorio rápida. Puedes ejecutar animaciones en el subproceso principal o en el trabajador. En el caso del subproceso principal, no puedes interactuar con el botón mientras se ejecuta la tarea pesada, ya que el subproceso está bloqueado. En el caso del trabajador, no hay ningún impacto en la capacidad de respuesta de la IU.

Demo

También funciona de la otra manera: el subproceso principal ocupado no influye en la animación que se ejecuta en un trabajador. Puedes usar esta función para evitar la inestabilidad visual y garantizar una animación fluida a pesar del tráfico del subproceso principal, como se muestra en la siguiente demostración.

Demo

En el caso de un lienzo normal, la animación se detiene cuando el subproceso principal se sobrecarga de forma artificial, mientras que OffscreenCanvas basado en trabajadores se reproduce sin problemas.

Debido a que la API de OffscreenCanvas es generalmente compatible con el elemento Canvas normal, puedes usarla como una mejora progresiva, también con algunas de las bibliotecas gráficas líderes del mercado.

Por ejemplo, puedes detectarlo y, si está disponible, usarlo con Three.js especificando la opción de lienzo en el constructor del renderizador:

const canvasEl = document.querySelector('canvas');
const canvas =
 
'OffscreenCanvas' in window
   
? canvasEl.transferControlToOffscreen()
   
: canvasEl;
canvas
.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});

El único inconveniente es que Three.js espera que el lienzo tenga una propiedad style.width y style.height. OffscreenCanvas, como está completamente separado del DOM, no lo tiene, por lo que debes proporcionarlo por tu cuenta, ya sea creando un stub o proporcionando una lógica que vincule estos valores a las dimensiones del lienzo original.

A continuación, se muestra cómo ejecutar una animación básica de Three.js en un trabajador:

Demo

Ten en cuenta que algunas de las APIs relacionadas con el DOM no están disponibles de inmediato en un trabajador, por lo que, si quieres usar funciones más avanzadas de Three.js, como texturas, es posible que necesites más soluciones alternativas. Si quieres obtener algunas ideas para comenzar a experimentar con ellas, mira el video de Google I/O 2017.

Si usas mucho las capacidades gráficas del lienzo, OffscreenCanvas puede influir de forma positiva en el rendimiento de tu app. Poner los contextos de renderización del lienzo a disposición de los trabajadores aumenta el paralelismo en las aplicaciones web y aprovecha mejor los sistemas multinúcleo.

Recursos adicionales