Sempre foi você, Canvas2D

Aaron Krajeski
Aaron Krajeski

Em um mundo de shaders, malhas e filtros, o Canvas2D pode não ser tão interessante. Mas deveria estar. Entre 30% e 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, em geladeiras e no espaço (sério).

É verdade que a API está um pouco desatualizada em relação ao desenho 2D de última geração. Felizmente, trabalhamos muito para implementar novos recursos no Canvas2D e acompanhar o CSS, simplificar a ergonomia e melhorar a performance.

Parte 1: atualização do CSS

O CSS tem alguns comandos de desenho que estão faltando na Canvas2D. Com a nova API, adicionamos alguns dos recursos mais solicitados:

Retângulo arredondado

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

Os retângulos arredondados são extremamente úteis: como botões, balões de chat, miniaturas, balões de fala, você escolhe. Sempre foi possível fazer um retângulo arredondado no Canvas2D, mas era 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 foi necessário para um retângulo arredondado simples e modesto:

Um retângulo arredondado.

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

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

Portanto, o exemplo acima pode ser substituído por:

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

O método ctx.roundRect() também recebe 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 no CSS. Exemplo:

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

Confira a demonstração para brincar.

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 e um gradiente cônico legal?

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 de Canvas2Ds ficaram muito atrasados. O Chrome adicionou vários novos atributos à renderização de texto do Canvas2D:

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

Parte 2: ajustes ergonômicos

Antes, algumas coisas com o Canvas2D eram possíveis, mas a implementação era desnecessariamente complicada. 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 retro:

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 limpar uma tela? Boa! ctx.clearRect(), é claro.

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

Não funcionou. Boa! Primeiro, preciso redefinir a transformação:

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

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

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

Uma linha horizontal e uma diagonal.

Grrrr! Isso não está certo. 😡 O que essa linha extra está fazendo aqui? Além disso, por que ele está rosa? Ok, vamos verificar o StackOverflow.

canvas.width = canvas.width;

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

Mas isso acabou. Com a nova API, temos uma API simples, elegante e inovadora:

ctx.reset();

Lamentamos a demora.

Filtros

Os filtros SVG são um mundo à parte. Se você não conhece esse recurso, recomendamos a leitura de 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 Canvas2D. Você só precisa 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 atrapalha bastante o nosso padrão:

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

Mas e se você quiser fazer o que foi mencionado acima, mas permanecer no JavaScript e não mexer com 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 },
]);

Simples assim. Teste e brinque com os parâmetros na demonstração aqui.

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 um controle mais detalhado dos sites e permitir as taxas de quadros mais suaves possíveis:

Vai ler com frequência

Use getImageData() para ler os dados de pixels de uma tela. Isso 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 as coisas em segundo plano e manter a tela rápida para uma variedade maior de casos de uso. Esse recurso já existe há algum tempo no Firefox, e agora ele faz parte da especificação da tela.

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

Perda de contexto

Vamos deixar as guias tristes felizes de novo! Caso um cliente fique sem memória da GPU ou algum outro problema ocorra na tela, agora é possível receber um callback e redesenhar 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 o contexto e a perda de tela, o WHATWG tem uma boa explicação no wiki.

Conclusão

Não importa se você é novo no Canvas2D, se já o usa há anos ou se evita usá-lo há anos, dê outra olhada no canvas. É a API vizinha que estava lá o tempo todo.