OffscreenCanvas: agilize suas operações com um web worker

Tim Dresser

A tela é uma maneira comum de exibir todos os tipos de gráficos na tela e um ponto de entrada para o mundo do WebGL. Ele pode ser usado para desenhar formas, imagens, executar animações ou até exibir e processar conteúdo de vídeo. Ele é usado com frequência para criar experiências de usuário incríveis em aplicativos da Web com muitos recursos de mídia e jogos on-line.

Ele é programável, o que significa que o conteúdo desenhado na tela pode ser criado de forma programática, por exemplo, em JavaScript. Isso dá à tela uma grande flexibilidade.

Ao mesmo tempo, em sites modernos, a execução de script é uma das fontes mais frequentes de problemas de responsividade do usuário. Como a lógica e a renderização da tela acontecem na mesma linha de execução que a interação do usuário, as computações (às vezes pesadas) envolvidas nas animações podem prejudicar o desempenho real e percebido do app.

Felizmente, OffscreenCanvas é uma resposta a essa ameaça.

Compatibilidade com navegadores

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

Origem

Antes, os recursos de desenho de tela estavam vinculados ao elemento <canvas>, o que significava que ele dependia diretamente do DOM. A OffscreenCanvas, como o nome indica, separa o DOM e a API Canvas movendo-os para fora da tela.

Graças a esse desacoplamento, a renderização de OffscreenCanvas é totalmente separada do DOM e oferece algumas melhorias de velocidade em relação à tela normal, já que não há sincronização entre as duas.

Além disso, ele pode ser usado em um Web Worker, mesmo que não haja um DOM disponível. Isso permite todos os tipos de casos de uso interessantes.

Usar OffscreenCanvas em um worker

Os workers são a versão da Web das linhas de execução. Eles permitem executar tarefas em segundo plano.

Mover parte do script para um worker dá ao app mais espaço para executar tarefas críticas do usuário na linha de execução principal. Sem a OffscreenCanvas, não era possível usar a API Canvas em um worker, porque não havia um DOM disponível.

A OffscreenCanvas não depende do DOM, então ela pode ser usada. O exemplo a seguir usa OffscreenCanvas para calcular uma cor de gradiente em um worker:

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

Desbloquear a linha de execução principal

Mover cálculos pesados para um worker permite liberar recursos significativos na linha de execução principal. Use o método transferControlToOffscreen para espelhar a tela normal em uma instância OffscreenCanvas. As operações aplicadas a OffscreenCanvas serão renderizadas na tela de origem automaticamente.

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

No exemplo abaixo, o cálculo pesado acontece quando o tema de cores está mudando. Ele leva alguns milissegundos, mesmo em um computador rápido. Você pode executar animações na linha de execução principal ou no worker. No caso da linha de execução principal, não é possível interagir com o botão enquanto a tarefa pesada está em execução. A linha de execução está bloqueada. No caso do worker, não há impacto na resposta da interface.

Demo

Funciona da mesma forma: a linha de execução principal ocupada não influencia a animação em execução em um worker. Você pode usar esse recurso para evitar o problema de instabilidade visual e garantir uma animação suave, mesmo com o tráfego da linha de execução principal, conforme mostrado na demonstração a seguir.

Demo

No caso de uma tela regular, a animação é interrompida quando a linha de execução principal fica artificialmente sobrecarregada, enquanto a OffscreenCanvas baseada em worker é reproduzida sem problemas.

Como a API OffscreenCanvas geralmente é compatível com o elemento Canvas normal, ela pode ser usada como um aprimoramento progressivo, também com algumas das principais bibliotecas gráficas do mercado.

Por exemplo, você pode detectar o recurso e, se disponível, usá-lo com o Three.js especificando a opção de tela no construtor do 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});

O problema é que o Three.js espera que a tela tenha uma propriedade style.width e style.height. A OffscreenCanvas, totalmente desconectada do DOM, não tem isso. Portanto, você precisa fornecer isso, seja substituindo-a ou fornecendo uma lógica que conecte esses valores às dimensões originais da tela.

Confira a seguir como executar uma animação básica do Three.js em um worker:

Demo

Algumas das APIs relacionadas ao DOM não estão disponíveis em um worker. Portanto, se você quer usar recursos mais avançados do Three.js, como texturas, talvez seja necessário usar mais soluções alternativas. Para ter algumas ideias sobre como começar a experimentar esses recursos, confira o vídeo do Google I/O 2017.

Se você estiver usando muito os recursos gráficos da tela, a OffscreenCanvas poderá influenciar positivamente o desempenho do app. Disponibilizar contextos de renderização de tela para os workers aumenta o paralelismo em aplicativos da Web e faz melhor uso de sistemas multicore.

Outros recursos