Sempre foi você, Canvas2D

Aaron krajeski
Aaron Krajeski

Em um mundo de sombreadores, mesh e filtros, o Canvas2D pode não animar você. Mas deveria acontecer! 30% a 40% das páginas da Web têm um elemento <canvas>, e 98% de todas as telas usam um contexto de renderização Canvas2D. Há Canvas2Ds em carros, geladeiras e no espaço (realmente).

É certo que a API está um pouco atrasada quando se trata de desenhos 2D de última geração. Felizmente, estamos trabalhando muito na implementação de novos recursos no Canvas2D para acompanhar o CSS, agilizar a ergonomia e melhorar o desempenho.

Parte 1: atualização com o CSS

O CSS tem alguns comandos de desenho que faltam muito no Canvas2D. Com a nova API, adicionamos vários dos recursos mais solicitados:

Retângulo arredondado

Retângulos arredondados: a base da Internet, da computação, perto da civilização.

Falando sério, retângulos arredondados são extremamente úteis: como botões, balões de chat, miniaturas, balões de diálogo, entre outros. Sempre foi possível fazer um retângulo arredondado no Canvas2D, mas ficava um pouco confuso:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'magenta';

const top = 10;
const left = 10;
const width = 200;
const height = 100;
const radius = 20;

ctx.beginPath();
ctx.moveTo(left + radius, top);
ctx.lineTo(left + width - radius, top);
ctx.arcTo(left + width, top, left + width, top + radius, radius);
ctx.lineTo(left + width, top + height - radius);
ctx.arcTo(left + width, top + height, left + width - radius, top + height, radius);
ctx.lineTo(left + radius, top + height);
ctx.arcTo(left, top + height, left, top + height - radius, radius);
ctx.lineTo(left, top + radius);
ctx.arcTo(left, top, left + radius, top, radius);
ctx.stroke();

Tudo isso era necessário para um retângulo arredondado simples:

Um retângulo arredondado.

Com a nova API, há um método roundRect().

ctx.roundRect(upper, left, width, height, borderRadius);

Portanto, o código acima pode ser totalmente substituído por:

ctx.roundRect(10, 10, 200, 100, 20);

O método ctx.roundRect() também aceita uma matriz para o argumento borderRadius de até quatro números. Esses raios controlam os quatro cantos do retângulo arredondado da mesma maneira que em CSS. Exemplo:

ctx.roundRect(10, 10, 200, 100, [15, 50, 30]);

Confira a demonstração.

Gradiente cônico

Você já viu gradientes lineares:

const gradient = ctx.createLinearGradient(0, 0, 200, 100);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(0.5, 'magenta');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);

Um gradiente linear.

Gradientes radiais:

const radialGradient = ctx.createRadialGradient(150, 75, 10, 150, 75, 70);
radialGradient.addColorStop(0, 'white');
radialGradient.addColorStop(0.5, 'magenta');
radialGradient.addColorStop(1, 'lightblue');

ctx.fillStyle = radialGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);

Um gradiente radial.

Mas que tal um bom gradiente cônico?

const grad = ctx.createConicGradient(0, 100, 100);

grad.addColorStop(0, 'red');
grad.addColorStop(0.25, 'orange');
grad.addColorStop(0.5, 'yellow');
grad.addColorStop(0.75, 'green');
grad.addColorStop(1, 'blue');

ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 200);

Um gradiente cônico.

Modificadores de texto

Os recursos de renderização de texto do Canvas2Ds estavam lamentáveis. O Chrome adicionou vários novos atributos à renderização de texto Canvas2D:

Todos esses atributos correspondem às contrapartes do CSS com os mesmos nomes.

Parte 2: ajustes ergonômicos

Anteriormente, algumas coisas eram possíveis com o Canvas2D, mas desnecessariamente complicadas de implementar. Confira algumas melhorias na qualidade de vida para desenvolvedores de JavaScript que querem usar o Canvas2D:

Redefinição de contexto

Para explicar como limpar uma tela, escrevi uma pequena função para desenhar um padrão retrô (link em inglês):

draw90sPattern();

Um padrão retrô de triângulos e quadrados.

Ótimo! Agora que terminei esse padrão, quero limpar a tela e desenhar outra coisa. Como podemos limpar uma tela de novo? Fui, sim! ctx.clearRect(), é claro.

ctx.clearRect(0, 0, canvas.width, canvas.height);

Ah... isso não funcionou. Fui, sim! Primeiro, preciso redefinir a transformação:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Uma tela em branco.

Perfeito! Uma boa tela em branco. Agora vamos começar a desenhar uma linha horizontal bem legal:

ctx.moveTo(10, 10);
ctx.lineTo(canvas.width, 10);
ctx.stroke();

Uma linha horizontal e uma diagonal.

Grrrr! Incorreto! 📔 O que essa linha extra faz aqui? E por que é rosa? Certo, vamos conferir o StackOverflow.

canvas.width = canvas.width;

Por que isso é tão bobeiro? Por que isso é tão difícil?

Bem, não é mais nada. Com a nova API, temos a inovação simples, elegante e bonita:

ctx.reset();

Lamentamos a demora.

Filtros

Os filtros SVG são um mundo todo só. Se esse não for seu caso, recomendo que você leia The Art Of SVG Filters And Why It Is Awesome, que mostra um pouco do potencial incrível deles.

Os filtros de estilo SVG já estão disponíveis para o Canvas2D. Basta estar disposto a transmitir o filtro como um URL que aponta para outro elemento de filtro SVG na página:

<svg>
  <defs>
    <filter id="svgFilter">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
      <feConvolveMatrix kernelMatrix="-3 0 0 0 0.5 0 0 0 3" />
      <feColorMatrix type="hueRotate" values="90" />
    </filter>
  </defs>
</svg>
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 400;
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

ctx.filter = "url('#svgFilter')";
draw90sPattern(ctx);

O que estraga muito nosso padrão:

O padrão retrô com um efeito desfocado aplicado.

Mas, e se você quisesse fazer o que foi descrito acima e permanecer no JavaScript sem mexer nas strings? Com a nova API, isso é totalmente possível.

ctx.filter = new CanvasFilter([
  { filter: 'gaussianBlur', stdDeviation: 5 },
  {
    filter: 'convolveMatrix',
    kernelMatrix: [
      [-3, 0, 0],
      [0, 0.5, 0],
      [0, 0, 3],
    ],
  },
  { filter: 'colorMatrix', type: 'hueRotate', values: 90 },
]);

Superfácil! Teste e teste com os parâmetros nesta demonstração.

Parte 3: melhorias de desempenho

Com a nova API Canvas2D, também queríamos melhorar o desempenho sempre que possível. Adicionamos alguns recursos para dar aos desenvolvedores controle mais preciso dos sites e permitir os melhores frames possíveis:

Lerá com frequência

Use getImageData() para ler dados de pixels de uma tela. Pode ser muito lento. A nova API oferece uma maneira de marcar explicitamente uma tela para leitura (para efeitos generativos, por exemplo). Isso permite otimizar internamente e manter a tela rápida para uma variedade maior de casos de uso. Esse recurso já está no Firefox há algum tempo e finalmente estamos finalmente tornando-o parte da especificação do canvas.

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });

Perda de contexto

Vamos deixar guias tristes felizes de novo! Caso um cliente fique sem memória de GPU ou algum outro desastre ocorra na tela, você pode receber um callback e desenhar novamente conforme necessário:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

canvas.addEventListener('contextlost', onContextLost);
canvas.addEventListener('contextrestored', redraw);

Se você quiser saber mais sobre contexto e perda da tela, o WhatWG tem uma boa explicação (link em inglês) no wiki deles.

Conclusão

Não importa se você é iniciante, se usa há anos ou se evita usá-lo há anos, estou aqui para dizer para você dar outra olhada no canvas. É a API próxima que está lá o tempo todo.

Agradecimentos

Imagem principal de Sandie Clarke no Unsplash (links em inglês).