Sei sempre stato tu, Canvas2D

Aaron Krajeski
Aaron Krajeski

In un mondo di shader, mesh e filtri, Canvas2D potrebbe non entusiasmarti. ma dovrebbe esserlo. Il 30-40% delle pagine web contiene un elemento <canvas> e il 98% di tutti i canvas utilizza un contesto di rendering Canvas2D. Esistono Canvas2D nelle auto, nei frigoriferi e nello spazio (in realtà).

Bisogna ammettere che l'API è un po' indietro rispetto alle ultime novità in fatto di disegno 2D. Fortunatamente abbiamo lavorato duramente per implementare nuove funzionalità in Canvas2D per recuperare CSS, snellire l'ergonomia e migliorare le prestazioni.

Parte 1: aggiornamento con CSS

CSS ha alcuni comandi di disegno che mancano a Canvas2D. Con la nuova API abbiamo aggiunto alcune delle funzionalità più richieste:

Rettangolo arrotondato

Rettangoli arrotondati: la pietra angolare di internet, dell'informatica, della civiltà.

A parte gli scherzi, i rettangoli arrotondati sono estremamente utili: come pulsanti, bolle di chat, miniature, bolle di dialogo e così via. È sempre stato possibile creare un rettangolo arrotondato in Canvas2D, ma era un po' complicato:

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

Tutto questo era necessario per un rettangolo arrotondato semplice e modesto:

Un rettangolo arrotondato.

Con la nuova API è disponibile un metodo roundRect().

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

Pertanto, quanto sopra può essere completamente sostituito da:

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

Il metodo ctx.roundRect() accetta anche un array per l'argomento borderRadius di massimo quattro numeri. Questi raggi controllano i quattro angoli del rettangolo arrotondato nello stesso modo come per il CSS. Ad esempio:

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

Dai un'occhiata alla demo per provare.

Gradiente conico

Hai visto le sfumature lineari:

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

Un gradiente lineare.

Sfumature radiali:

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

Un gradiente radiale.

Ma che dire di un bel gradiente conico?

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

Un gradiente conico.

Modificatori di testo

Le funzionalità di rendering del testo di Canvas2D sono molto indietro. Chrome ha aggiunto diversi nuovi attributi al rendering del testo Canvas2D:

Questi attributi corrispondono a quelli CSS con gli stessi nomi.

Parte 2: modifiche ergonomiche

In precedenza, alcune operazioni con Canvas2D erano possibili, ma inutilmente complesse da implementare. Di seguito sono riportati alcuni miglioramenti della qualità di vita per gli sviluppatori JavaScript che vogliono utilizzare Canvas2D:

Reimpostazione del contesto

Per spiegare come cancellare una tela, ho scritto una piccola funzione per disegnare un motivo retrò:

draw90sPattern();

Un motivo retrò di triangoli e quadrati.

Bene. Ora che ho finito con questo motivo, voglio cancellare la tela e disegnare qualcos\'altro. Aspetta, come faccio a cancellare una tela? Oh sì! ctx.clearRect(), certo.

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

Huh... non ha funzionato. Oh sì! Devo prima reimpostare la trasformazione:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Una tela vuota.

Perfetto! Una bella tela bianca. Ora iniziamo a disegnare una bella linea orizzontale:

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

Una linea orizzontale e una diagonale.

Grrrr! Non è corretto! 😡 Che cosa ci fa questa riga in più? Inoltre, perché è rosa? Ok, controlla su StackOverflow.

canvas.width = canvas.width;

Perché è così sciocco? Perché è così difficile?

Beh, non più. Con la nuova API abbiamo una soluzione semplice, elegante e bella, che rappresenta una vera e propria rivoluzione:

ctx.reset();

Ci dispiace molto.

Filtri

I filtri SVG sono un mondo a parte. Se non li hai mai utilizzati, ti consiglio di leggere The Art Of SVG Filters And Why It Is Awesome, che mostra alcune delle loro straordinarie potenzialità.

I filtri di stile SVG sono già disponibili per Canvas2D. Devi solo avere la volontà di passare il filtro come un URL che punta a un altro elemento filtro SVG nella pagina:

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

Il che rovina il nostro pattern:

Il motivo retrò con un effetto sfocatura applicato.

E se volessi eseguire quanto sopra, ma rimanere in JavaScript e non confondere le stringhe? Con la nuova API, questo è del tutto possibile.

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

Un gioco da ragazzi! Prova e gioca con i parametri nella demo qui.

Parte 3: miglioramenti delle prestazioni

Con la nuova API Canvas2D, volevamo anche migliorare le prestazioni, ove possibile. Abbiamo aggiunto alcune funzionalità per offrire agli sviluppatori un controllo più granulare dei loro siti web e consentire le framerate più fluide possibili:

Leggerà spesso

Utilizza getImageData() per leggere i dati dei pixel da una tela. Può essere molto lento. La nuova API ti offre un modo per contrassegnare esplicitamente una tela per la lettura (ad esempio per gli effetti generativi). In questo modo puoi ottimizzare il funzionamento interno e mantenere la velocità di Canvas per una maggiore varietà di casi d'uso. Questa funzionalità è disponibile in Firefox da un po' di tempo e finalmente è stata inserita nelle specifiche del canvas.

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

Perdita di contesto

Rendiamo di nuovo felici le schede tristi. Se un client esaurisce la memoria della GPU o si verifica un altro problema con la tela, ora puoi ricevere un callback e ridisegnare la tela in base alle esigenze:

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

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

Se vuoi saperne di più sul contesto e sulla perdita di canvas, WHATWG fornisce una buona spiegazione nella sua wiki.

Conclusione

Che tu non abbia mai utilizzato Canvas2D, lo stia usando da anni o lo stia evitando da anni, ti consiglio di dare un'altra occhiata a Canvas. È l'API di cui hai sempre avuto bisogno.