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 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, a także cywilizacji.

Poważnie mówiąc, zaokrąglone prostokąty są bardzo przydatne: jako przyciski, okienka czatu, miniatury, dymki dialogowe – co tylko chcesz. 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();

Wszystko to było konieczne do stworzenia skromnego, prostego zaokrąglonego prostokąta:

Zaokrąglony prostokąt.

W nowym interfejsie API 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

Wyświetlono 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 jaki jest gradient stożkowy?

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 bardzo ograniczone 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ą korzystać z Canvas2D:

Resetowanie kontekstu

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

draw90sPattern();

Wzór w stylu retro z trójkątów i kwadratów.

Świetnie! Skończyłem już z tym wzorem, więc chcę wyczyścić obszar roboczy i narysować coś innego. Poczekaj, jak zresetować płótno? O tak! ctx.clearRect(), oczywiście.

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

Nie udało się. 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 to za dodatkowa linia? Dlaczego jest różowy? OK, sprawdźmy Stack Overflow.

canvas.width = canvas.width;

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

Już nie. Nowy interfejs API to proste, eleganckie i przełomowe rozwiązanie:

ctx.reset();

Przepraszam, że to tyle trwało.

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 przekażesz filtr jako adres URL wskazujący na 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 w stylu retro z efektem rozmycia.

Co jednak, jeśli chcesz wykonać powyższe czynności, ale nie chcesz używać ciągów znaków w JavaScript? Dzięki nowemu interfejsowi API jest to 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 bułka z masłem. Wypróbuj i zmodyfikuj parametry w tej wersji demonstracyjnej.

Część 3. Poprawa wydajności

W przypadku nowego interfejsu Canvas2D API chcieliśmy też w miarę możliwości zwiększyć wydajność. 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ę:

Czytać często

Użyj getImageData(), aby odczytać dane pikseli z płótna. Może to być bardzo powolne. Nowe API umożliwia jawne oznaczanie płóta do odczytu (np. do efektów generatywnych). Umożliwi Ci to optymalizację działania pod maską i utrzymanie płynności działania kanwy w różnych przypadkach użycia. Ta funkcja jest dostępna w Firefox od jakiegoś czasu, a w końcu dodaliśmy ją do specyfikacji canvasa.

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ć obraz:

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 w wiki WHATWG.

Podsumowanie

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