Jij bent het altijd geweest, Canvas2D

Aaron Krajeski
Aaron Krajeski

In een wereld vol shaders, meshes en filters word je misschien niet enthousiast van Canvas2D. Maar dat zou het wel moeten zijn! 30-40% van de webpagina's heeft een <canvas> -element en 98% van alle canvassen gebruikt een Canvas2D-rendercontext. Er zijn Canvas2D's in auto's, op koelkasten en in de ruimte (echt waar).

Toegegeven, de API loopt wat achter op het gebied van state-of-the-art 2D-tekenen. Gelukkig hebben we hard gewerkt aan de implementatie van nieuwe functies in Canvas2D om de CSS-standaard te evenaren, de ergonomie te stroomlijnen en de prestaties te verbeteren.

Deel 1: CSS onder de loep

CSS heeft een paar tekencommando's die in Canvas2D node ontbreken. Met de nieuwe API hebben we een handvol van de meest gevraagde functies toegevoegd:

Ronde rechthoek

Afgeronde rechthoeken: de hoeksteen van het internet, van computergebruik, van de beschaving.

Alle gekheid op een stokje, afgeronde rechthoeken zijn enorm handig: als knoppen, chatballonnen, miniaturen, tekstballonnen, noem maar op. Het is altijd al mogelijk geweest om een ​​afgeronde rechthoek te maken in Canvas2D, het was alleen een beetje rommelig:

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

Dit alles was nodig voor een bescheiden, eenvoudige afgeronde rechthoek:

Een afgeronde rechthoek.

De nieuwe API bevat een roundRect() methode.

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

Het bovenstaande kan dus geheel vervangen worden door:

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

De ctx.roundRect() -methode accepteert ook een array voor het borderRadius -argument van maximaal vier getallen. Deze radiussen bepalen de vier hoeken van de afgeronde rechthoek op dezelfde manier als CSS . Bijvoorbeeld:

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

Probeer de demo uit en probeer het uit !

Conische gradiënt

Je hebt lineaire gradiënten gezien:

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

Een lineaire gradiënt.

Radiale gradiënten:

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

Een radiale gradiënt.

Maar wat dacht je van een mooie kegelvormige gradiënt?

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

Een kegelvormige gradiënt.

Tekstmodificatoren

De mogelijkheden voor tekstweergave van Canvas2D liepen hopeloos achter. Chrome heeft verschillende nieuwe kenmerken toegevoegd aan de tekstweergave van Canvas2D:

Al deze kenmerken komen overeen met hun CSS-tegenhangers met dezelfde naam.

Deel 2: ergonomische aanpassingen

Voorheen waren sommige dingen met Canvas2D mogelijk, maar onnodig ingewikkeld om te implementeren. Hier zijn enkele verbeteringen voor JavaScript-ontwikkelaars die Canvas2D willen gebruiken:

Context reset

Om uit te leggen hoe je een canvas schoonmaakt, heb ik een leuke kleine functie geschreven om een ​​retropatroon te tekenen :

draw90sPattern();

Een retropatroon van driehoeken en vierkanten.

Geweldig! Nu ik klaar ben met dat patroon, wil ik het canvas leegmaken en iets anders tekenen. Wacht, hoe maken we ook alweer een canvas leeg? O ja! ctx.clearRect() natuurlijk.

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

Huh... dat werkte niet. Oh ja! Ik moet eerst de transformatie resetten:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Een leeg canvas.

Perfect! Een mooi leeg canvas. Laten we nu een mooie horizontale lijn tekenen:

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

Een horizontale en een diagonale lijn.

Grrrr! Dat klopt niet! 😡 Wat doet die extra regel hier? En waarom is hij roze? Oké, laten we StackOverflow eens bekijken.

canvas.width = canvas.width;

Waarom is dit zo dwaas? Waarom is dit zo moeilijk?

Nou, dat is niet meer zo. Met de nieuwe API hebben we de eenvoudige, elegante, prachtige baanbrekende:

ctx.reset();

Sorry dat het zo lang duurde.

Filters

SVG-filters zijn een wereld op zich. Als je ze nog niet kent, raad ik je ten zeerste aan om 'The Art Of SVG Filters And Why It Is Awesome' te lezen, waarin ze hun verbluffende potentieel laten zien.

SVG-stijlfilters zijn al beschikbaar voor Canvas2D! Je hoeft alleen maar bereid te zijn het filter door te geven als een URL die verwijst naar een ander SVG-filterelement op de 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);

En dat verstoort ons patroon behoorlijk:

Het retropatroon met een wazig effect toegepast.

Maar wat als je bovenstaande wilt doen, maar binnen JavaScript wilt blijven en niet met strings wilt rommelen? Met de nieuwe API is dit absoluut mogelijk.

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

Zo makkelijk als een fluitje van een cent! Probeer het en speel met de parameters in de demo hier .

Deel 3: prestatieverbeteringen

Met de nieuwe Canvas2D API wilden we ook de prestaties waar mogelijk verbeteren. We hebben een aantal functies toegevoegd om ontwikkelaars meer controle over hun websites te geven en de meest vloeiende framerates mogelijk te maken:

Zal regelmatig lezen

Gebruik getImageData() om pixeldata terug te lezen van een canvas. Dit kan erg traag zijn. De nieuwe API biedt je de mogelijkheid om een ​​canvas expliciet te markeren voor teruglezen (bijvoorbeeld voor generatieve effecten). Dit stelt je in staat om dingen onder de motorkap te optimaliseren en het canvas snel te houden voor een grotere verscheidenheid aan toepassingen. Deze functie bestaat al een tijdje in Firefox en we maken het nu eindelijk onderdeel van de canvasspecificatie.

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

Contextverlies

Laten we trieste tabbladen weer vrolijk maken! Mocht een client geen GPU-geheugen meer hebben of gebeurt er iets anders met je canvas, dan kun je nu een callback ontvangen en indien nodig opnieuw tekenen:

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

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

Als je meer wilt lezen over canvascontext en verlies, heeft de WHATWG een goede uitleg op hun wiki.

Conclusie

Of je nu nieuw bent met Canvas2D, het al jaren gebruikt of het al jaren vermijdt, ik raad je aan om Canvas nog eens te bekijken. Het is de API van hiernaast die er altijd al is geweest.