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, sulle lavagne frigo, e nello spazio (davvero).

Bisogna ammettere che l'API è un po' indietro rispetto alle ultime novità nel campo del disegno 2D. Fortunatamente, ci siamo impegnati a implementare nuove funzionalità in Canvas2D per raggiungere il CSS, semplificare l'ergonomia e migliorare il rendimento.

Parte 1: aggiornamento del 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, quasi 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 come avviene 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);

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

Una sfumatura radiale.

E che ne dici 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:

Tutti questi attributi corrispondono alle loro controparti CSS con gli stessi nomi.

Parte 2: modifiche ergonomiche

In precedenza, alcune cose con Canvas2D erano possibili, ma la loro implementazione era eccessivamente complicata. Ecco alcuni miglioramenti della qualità della 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);

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 vuota. 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 è giusto. 😡 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();

Mi dispiace che ci abbia impiegato così tanto tempo.

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 essere disposto a passare il filtro come URL che rimanda a un altro elemento di 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 di sfocatura applicato.

Ma cosa succederebbe se volessi fare quanto sopra, ma rimanere in JavaScript e non dover fare i conti con 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 },
]);

È facilissimo. 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:

Verrà letto di frequente

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 Canvas veloce per una maggiore varietà di casi d'uso. Questa funzionalità è presente in Firefox da un po' di tempo e finalmente la stiamo rendendo parte della specifica 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, che lo usi da anni o che eviti di utilizzarlo da anni, ti consiglio di dare un'altra occhiata a Canvas. È l'API di cui hai bisogno, che è sempre stata lì.