Canvas2D to zawsze Ty

Aaron Krajeski
Aaron Krajeski

W świecie cieniowania, siatek i filtrów Canvas2D może nie wzbudzać entuzjazmu. Ale powinno. 30–40% stron internetowych zawiera element <canvas>, a 98% wszystkich kanw używa kontekstu renderowania Canvas2D. Canvas2D można znaleźć w samochodach, na lodówkach, a nawet w kosmosie (naprawdę).

Trzeba przyznać, że interfejs API jest nieco przestarzały, jeśli chodzi o najnowocześniejsze rysowanie 2D. Na szczęście ciężko pracowaliśmy nad wdrożeniem nowych funkcji w Canvas2D, aby dorównać CSS, usprawnić ergonomię i poprawić wydajność.

Część 1. Wprowadzenie do CSS

CSS ma kilka poleceń do rysowania, których bardzo brakuje w Canvas2D. W ramach nowego interfejsu API dodaliśmy kilka funkcji, o które najczęściej proszono:

Zaokrąglony prostokąt

Zaokrąglone prostokąty: podstawa internetu, komputerów i cywilizacji.

Ogólnie rzecz biorąc, zaokrąglone prostokąty są bardzo przydatne: jako przyciski, dymki czatu, miniatury czy dymki. Zaokrąglone prostokąty można było tworzyć w Canvas2D od zawsze, ale było to trochę niechlujne:

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

Do utworzenia skromnego, prostego zaokrąglonego prostokąta potrzebne było wszystko:

Zaokrąglony prostokąt.

W nowym interfejsie API dostępna jest metoda roundRect().

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

Powyższe informacje można całkowicie zastąpić następującymi:

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

Metoda ctx.roundRect() przyjmuje też tablicę jako argument borderRadius z maksymalnie 4 liczbami. Te promienie kontrolują 4 rogi zaokrąglonego prostokąta w taki sam sposób, jak w przypadku CSS. Na przykład:

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

Wypróbuj wersję demonstracyjną

Gradient stożkowy

Zauważyliśmy gradienty liniowe:

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

gradient liniowy.

Gradienty promieniowe:

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

gradient promieniowy.

A co z ładnym stożkowym gradientem?

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

Gradient stożkowy.

Modyfikatory tekstu

Canvas2D ma słabe możliwości renderowania tekstu. Do renderowania tekstu w Canvas2D w Chrome dodano kilka nowych atrybutów:

Wszystkie te atrybuty odpowiadają atrybutom usługi porównywania cen o tych samych nazwach.

Część 2. Ulepszenia ergonomii

Wcześniej niektóre rzeczy były możliwe w Canvas2D, ale ich implementacja była niepotrzebnie skomplikowana. Oto kilka ulepszeń dla programistów JavaScript, którzy chcą używać Canvas2D:

Resetowanie kontekstu

Aby wyjaśnić, jak wyczyścić płótno, napisałem głupią małą funkcję do rysowania wzoru retro:

draw90sPattern();

Retro wzór z trójkątów i kwadratów.

Świetnie. Teraz, gdy skończyłem z tym wzorem, chcę wyczyścić płótno i narysować coś innego. Poczekaj, jak zresetować płótno? O tak! ctx.clearRect(), oczywiście.

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

Ech... to nie zadziałało. O tak! Najpierw muszę zresetować transformację:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Puste płótno.

Super! Pusta przestrzeń do tworzenia. Teraz zacznijmy rysować ładną poziomą linię:

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

Linia pozioma i ukośna.

Grrrr! To nie jest poprawna odpowiedź. 😡 Co tu robi ten dodatkowy wiersz? Dlaczego jest różowy? OK, sprawdźmy Stack Overflow.

canvas.width = canvas.width;

Dlaczego to takie głupie? Dlaczego to takie trudne?

Nie ma już problemu. Nowy interfejs API to proste, eleganckie i przełomowe rozwiązanie:

ctx.reset();

Przepraszam, że zajęło to tak długo.

Filtry

Filtry SVG to zupełnie inny świat. Jeśli są one dla Ciebie nowością, zdecydowanie zalecam przeczytanie artykułu The Art Of SVG Filters And Why It Is Awesome, który pokazuje ich niesamowity potencjał.

Filtry stylu SVG są już dostępne w Canvas2D. Wystarczy, że zdecydujesz się przekazać filtr jako URL wskazujący inny element filtra SVG na stronie:

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

Co bardzo psuje nasz wzór:

Wzór retro z zastosowanym efektem rozmycia.

Co jednak, jeśli chcesz wykonać powyższe czynności, ale nie chcesz używać łańcuchów znaków w JavaScript? Dzięki nowemu interfejsowi API jest to w pełni możliwe.

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

To bardzo proste! Wypróbuj i zmodyfikuj parametry tutaj.

Część 3. Poprawa skuteczności

Wprowadzenie nowego interfejsu Canvas2D API zależało również na zwiększeniu wydajności tam, gdzie to możliwe. Dodaliśmy kilka funkcji, które dają deweloperom większą kontrolę nad ich witrynami i umożliwiają uzyskanie jak najwyższej możliwej liczby klatek na sekundę:

Będą często czytać

Użyj getImageData(), aby odczytywać dane pikseli z obiektu canvas. Może to być bardzo powolne. Nowe API umożliwia jawne oznaczanie płótna na potrzeby odczytu (np. do generowania efektów). W ten sposób zoptymalizujesz ogólne zasoby i zapewnisz szybkość działania obszaru roboczego w wielu różnych zastosowaniach. Ta funkcja jest dostępna w przeglądarce Firefox już od jakiegoś czasu, a właśnie dodajemy ją do specyfikacji canvas.

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

Utrata kontekstu

Zróbmy z smutnych kart znowu szczęśliwe! Jeśli klientowi skończy się pamięć GPU lub nastąpi inna katastrofa, możesz teraz otrzymać połączenie zwrotne i w razie potrzeby ponownie narysować:

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

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

Więcej informacji o kontekście i utracie danych na płótnie znajdziesz w dobrym artykule na wiki WHATWG.

Podsumowanie

Niezależnie od tego, czy dopiero zaczynasz korzystać z Canvas2D, czy używasz go od lat, czy też unikałeś/unikałaś go przez lata, zachęcam do ponownego przyjrzenia się tej platformie. To interfejs API, który był dostępny od samego początku.