Sempre foi você, Canvas2D

Aaron Krajeski
Aaron Krajeski

Em um mundo de sombreadores, malhas e filtros, o Canvas2D pode não animar você. Mas deveria! 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).

É claro que a API está um pouco atrasada em relação aos desenhos 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: atualizar-se sobre o 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. Isso fica 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);

Assim, o exposto acima pode ser totalmente 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 forma que para 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 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 de Canvas2Ds ficaram muito atrasados. O Chrome adicionou vários novos atributos à renderização de texto do Canvas2D:

Todos esses atributos correspondem aos atributos 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 a limpeza de uma tela, escrevi uma pequena função para desenhar um padrão retrô:

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 boa 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 está tão difícil?

Agora não é mais. 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 eles são novos para você, recomendamos ler The Art Of SVG Filters And why It Is Awesome, que mostra o incrível potencial deles.

Os filtros de estilo SVG já estão disponíveis para o Canvas2D. Você só precisa 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 atrapalha bastante o nosso padrão:

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

Mas e se você quiser fazer o que está 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 oferecer aos desenvolvedores um controle mais preciso dos sites e permitir as melhores taxas de frames 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á está no Firefox há algum tempo e agora faz parte da especificação da tela.

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

Perda de contexto

Vamos deixar guias tristes felizes outra vez! 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 ler mais sobre o contexto e a perda da tela, o WhatWG tem uma boa explicação (em inglês) na 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.