Siempre fuiste tú, Canvas2D

Aaron Karjeski
Aaron Krajeski

En un mundo de sombreadores, mallas y filtros, es posible que Canvas2D no te entusiasme. Pero debería ser así. Entre el 30% y el 40% de las páginas web tienen un elemento <canvas>, y el 98% de todos los lienzos usan un contexto de renderización de Canvas2D. Hay Canvas2Ds en automóviles, refrigeradores y en el espacio (en realidad).

Ciertamente, la API está un poco atrasada cuando se trata de dibujos en 2D de vanguardia. Afortunadamente, trabajamos arduamente para implementar funciones nuevas en Canvas2D para ponernos al día con CSS, optimizar la ergonomía y mejorar el rendimiento.

Parte 1: Ponerse al día con CSS

CSS tiene algunos comandos de dibujo que faltan en Canvas2D. Con la nueva API, agregamos algunas de las funciones más solicitadas:

Rectángulo redondeado

Rectángulos redondeados: la piedra angular de Internet, de la informática, de la civilización.

En serio, los rectángulos redondeados son extremadamente útiles: como botones, burbujas de chat, miniaturas, globos de diálogo, lo que sea. Siempre se pudo hacer un rectángulo redondeado en Canvas2D, pero se volvió un poco desordenado:

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();

Todo esto era necesario para crear un simple rectángulo redondeado:

Es un rectángulo redondeado.

Con la nueva API, existe un método roundRect().

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

Por lo tanto, lo anterior se puede reemplazar por completo con lo siguiente:

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

El método ctx.roundRect() también incluye un array para el argumento borderRadius de hasta cuatro números. Estos radios controlan las cuatro esquinas del rectángulo redondeado de la misma manera que en CSS. Por ejemplo:

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

Mira la demostración y prueba.

Gradiente cónico

Has visto gradientes lineales:

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

Gradiente lineal.

Gradientes radiales:

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

Gradiente radial.

Pero ¿qué pasa con un gradiente cónico agradable?

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

Gradiente cónico.

Modificadores de texto

Las capacidades de procesamiento de texto de Canvas2D están muy retrasadas. Chrome agregó varios atributos nuevos al procesamiento de texto de Canvas2D:

Todos estos atributos coinciden con sus equivalentes CSS con los mismos nombres.

Parte 2: Ajustes ergonómicos

Anteriormente, algunas cosas con Canvas2D eran posibles, pero su implementación era innecesariamente complicada. A continuación, se muestran algunas mejoras en la calidad de vida para los desarrolladores de JavaScript que desean usar Canvas2D:

Se restableció el contexto

Para explicar cómo despejar un lienzo, escribí una pequeña función absurda para dibujar un patrón retro:

draw90sPattern();

Patrón retro de triángulos y cuadrados.

Perfecto. Ahora que terminé con ese patrón, quiero borrar el lienzo y dibujar algo más. Espera, ¿cómo volvemos a borrar un lienzo? ¡Genial! ctx.clearRect(), por supuesto.

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

No funcionó. ¡Genial! Primero, debemos restablecer la transformación:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Un lienzo en blanco.

¡Perfecto! Un lindo lienzo en blanco. Ahora, comencemos a dibujar una bonita línea horizontal:

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

Una línea horizontal y una diagonal.

Grrrr. Respuesta incorrecta 🎂 ¿Qué hace esa línea adicional aquí? Además, ¿por qué es rosa? Veamos StackOverflow.

canvas.width = canvas.width;

¿Por qué es tan tonta? ¿Por qué es tan difícil?

Bueno, ya no es así. Con la nueva API, tenemos la innovación sencilla, elegante y atractiva:

ctx.reset();

Lamentamos que haya tardado tanto.

Filtros

Los filtros SVG son un mundo en sí mismos. Si eres nuevo para ti, te recomendamos leer The Art Of SVG Filters And Why It Is Awesome, que muestra parte de su increíble potencial.

Los filtros de estilo SVG ya están disponibles para Canvas2D. Solo debes estar dispuesto a pasar el filtro como una URL que dirija a otro elemento de filtro SVG en la 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);

lo que desordena nuestro patrón bastante bien:

Patrón retro con un efecto desenfocado.

Pero, ¿y si quisieras hacer lo anterior y permanecer en JavaScript y no perder tiempo con cadenas? Con la nueva API, esto es totalmente posible.

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 },
]);

¡Fácil como el pastel! Pruébalo y juega con los parámetros en esta demostración.

Parte 3: Mejoras en el rendimiento

Con la API de New Canvas2D, también queríamos mejorar el rendimiento siempre que fuera posible. Agregamos algunas funciones para brindarles a los desarrolladores un control más detallado de sus sitios web y permitir la velocidad de fotogramas más fluida posible:

Lectura con frecuencia

Usa getImageData() para leer datos de píxeles de un lienzo. Puede ser muy lento. La nueva API te brinda una forma de marcar de forma explícita un lienzo para volver a leer (para efectos generativos, por ejemplo). De esta manera, puedes optimizar todo de manera interna y mantener la velocidad del lienzo para una mayor variedad de casos de uso. Esta función está en Firefox por un tiempo y, por fin, la incluiremos en la especificación de lienzo.

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

Pérdida de contexto

¡Hagamos felices a las pestañas tristes de nuevo! En el caso de que un cliente se quede sin memoria de la GPU o de algún otro desastre en tu lienzo, ahora puedes recibir una devolución de llamada y volver a dibujar según sea necesario:

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

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

Si quieres obtener más información sobre el contexto y la pérdida de lienzo, WhatWG tiene una buena explicación en su wiki.

Conclusión

No importa si es la primera vez que usas Canvas2D, lo usas hace años o hace años que evitas usarlo, estoy aquí para que le eches un vistazo al lienzo. Es la API de al lado que siempre ha estado ahí.

Agradecimientos

Hero image de Sandie Clarke en Unsplash.