Sei sempre stato tu, Canvas2D

Aaron Krajeski
Aaron Krajeski

In un mondo di mesh, mesh e filtri, Canvas2D potrebbe non farti entusiasmare. Ma dovrebbe! Il 30-40% delle pagine web ha un elemento <canvas> e il 98% di tutte le canvas utilizza un contesto del rendering Canvas2D. Ci sono Canvas2D nelle auto, sui frigoriferi e nello spazio (davvero).

Certo, l'API è un po' indietro rispetto ai tempi quando si tratta di disegni 2D all'avanguardia. Fortunatamente, abbiamo lavorato sodo per implementare nuove funzionalità in Canvas2D per restare al passo con CSS, semplificare l'ergonomia e migliorare le prestazioni.

Parte 1: aggiornamento con i CSS

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

Rettangolo rotondo

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

In tutta serietà, i rettangoli arrotondati sono estremamente utili: come pulsanti, fumetti di chat, miniature, fumetti, basta assegnare un nome. È sempre stato possibile creare un rettangolo arrotondato in Canvas2D, perché è stato un po' caotico:

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 ciò 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 con:

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 allo stesso modo di CSS. Ad esempio:

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

Dai un'occhiata alla demo

Gradiente conico

Hai notato gradienti 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.

Gradienti 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 ne dici di una bella sfumatura conica?

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 Canvas2Ds sono rimasti dolorosamente in ritardo. Chrome ha aggiunto diversi nuovi attributi al rendering del testo di Canvas2D:

Questi attributi corrispondono tutti alle rispettive controparti CSS con gli stessi nomi.

Parte 2: modifiche ergonomiche

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

Reimpostazione del contesto

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

draw90sPattern();

Una trama retrò di triangoli e quadrati.

Bene. Ora che ho finito con lo schema, voglio cancellare la tela e disegnare qualcos'altro. Aspetta, come facciamo di nuovo a cancellare i dati su una tela? Oh sì! ctx.clearRect(), ovviamente.

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

Uh... 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.

Accidenti! Non è corretto. Ұ Che cosa ci fa quella linea aggiuntiva qui? Inoltre, perché è rosa? Ok, controlliamo StackOverflow.

canvas.width = canvas.width;

Perché è così stupido? Perché è così difficile?

Beh, non è più così. La nuova API offre funzionalità semplici, eleganti e innovative:

ctx.reset();

Mi dispiace che ci sia voluto così tanto tempo.

Filtri

I filtri SVG sono un mondo a sé. Se non conoscete la vostra azienda, vi consiglio vivamente di leggere The Art Of SVG Filtri And Why It Is Awesome, che mostra alcune delle loro straordinarie potenzialità.

I filtri in stile SVG sono già disponibili per Canvas2D. Devi solo essere disponibile a passare il filtro sotto forma di URL che punta a un altro elemento filtro SVG sulla 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 altera piuttosto bene i nostri schemi:

Il motivo retrò con un effetto sfocato applicato.

E se volessimo fare quanto sopra ma rimanere all'interno di JavaScript e non scherzare con le stringhe? Con la nuova API, questo è assolutamente 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 },
]);

Una torta facile. Provalo e gioca con i parametri nella demo qui.

Parte 3: miglioramenti del rendimento

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

Leggerò di frequente

Usa getImageData() per leggere i dati dei pixel da una tela. Può essere molto lento. La nuova API ti offre un modo per contrassegnare esplicitamente un canvas per la lettura (ad esempio per gli effetti generativi). In questo modo puoi ottimizzare le cose da dietro e mantenere veloce la tela per una varietà più ampia di casi d'uso. Questa funzione è presente in Firefox da un po' di tempo e stiamo finalmente inserendola nelle specifiche delle canvas.

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

Perdita di contesto

Facciamo di nuovo felici le schede tristi! Nel caso in cui un client esaurisca la memoria GPU o altri disastri cadano nella tela, ora puoi ricevere una richiamata e ritracciare a seconda delle 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 ha una buona spiegazione sul suo wiki.

Conclusione

Che tu sia alle prime armi con Canvas2D, che tu lo usi da anni o che tu abbia evitato di usarlo da anni, sono qui per dirti di dare un'altra occhiata alle stampe su tela. È l'API accanto alla porta che ci è sempre stata.

Ringraziamenti

Immagine hero di Sandie Clarke su Unsplash.