Canvas2D to zawsze Ty

Aaron Krajeski
Aaron Krajeski

W świecie programów do cieniowania, siatek i filtrów Canvas2D może Cię nie ekscytować. Ale powinien. 30–40% stron internetowych zawiera element <canvas>, a 98% wszystkich obszarów roboczych korzysta z kontekstu renderowania Canvas2D. Obrazy Canvas2D są używane w samochodach, lodówkach i w kosmosie.

Jeśli chodzi o najnowocześniejsze rysunki 2D, interfejs API nieco się opóźnia. Na szczęście pracowaliśmy nad wdrażaniem nowych funkcji w Canvas, aby nadążyć za stylami CSS, uprościć ergonomię i poprawić wydajność.

Część 1. Nadrabianie zaległości z usługą porównywania cen

W CSS jest kilka poleceń rysowania, których w Canvas2D bardzo brakuje. W nowym interfejsie API dodaliśmy kilka funkcji, o które najczęściej prosiliśmy:

Okrągły prostokąt

Zaokrąglone prostokąty: kamień węglowy internetu, komputerów, cywilizacji.

Ogólnie rzecz biorąc, zaokrąglone prostokąty są niezwykle przydatne: można je nazwać przyciskami, dymkami czatu, miniaturami lub dymkami. W Canvas2D zawsze dało się zrobić zaokrąglony prostokąt, ale w tym filmie robi się trochę chaotycznie.

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 utworzenia skromnego, prostego zaokrąglonego prostokąta:

Zaokrąglony prostokąt.

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

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

Elementy powyższe można całkowicie zastąpić:

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

Metoda ctx.roundRect() przyjmuje również tablicę dla argumentu borderRadius składającego się z maksymalnie 4 liczb. Te promienie określają 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

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

Ale może warto użyć ładnego gradientu stożkowego?

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

Możliwości renderowania tekstu Canvas2Ds zostały strasznie w tyle. Chrome dodał do renderowania tekstu Canvas2D kilka nowych atrybutów:

Wszystkie te atrybuty są zgodne z ich odpowiednikami w CSS o tych samych nazwach.

Część 2. Ergonomiczne poprawki

Wcześniej niektóre czynności w Canvas były możliwe, ale ich wdrożenie nie było konieczne. Oto kilka ulepszeń poprawiających jakość życia programistów JavaScriptu, którzy chcą korzystać z Canvas2D:

Resetowanie kontekstu

Aby wyjaśnić czyszczenie odbitki na płótnie, napisałam śmieszną funkcję do rysowania wzoru retro:

draw90sPattern();

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

Świetnie. Gdy skończysz korzystać z wzoru, wyczyścić płótno i narysować coś innego. Zaraz, jak jeszcze raz wyczyścić odbitkę na płótnie? Super! ctx.clearRect().

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

Nie udało się. Super! Najpierw muszę zresetować przekształcenie:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Puste tło.

Super! Przyjemne, puste płótno. Zacznijmy rysować ładną poziomą linię:

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

Linia pozioma i ukośna.

Wrrr... To nie jest poprawna odpowiedź! 😡 Dlaczego ta dodatkowa linia tu robi? Dlaczego ten kolor jest różowy? Sprawdźmy tylko StackOverflow.

canvas.width = canvas.width;

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

To już przeszłość. Nowy interfejs API oferuje prosty, elegancki, piękny sposób na przełom:

ctx.reset();

Przepraszam, że tak długo to trwa.

Filtry

Filtry SVG to cały świat. Jeśli dopiero zaczynasz, zachęcam do zapoznania się z artykułem The Art Of SVG Filters And Why It Is Awesome, który pokazuje, jak niesamowity ich potencjał.

Filtry stylów SVG są już dostępne w Canvas2D. Musisz tylko zdecydować, że chcesz przekazać filtr jako adres URL prowadzący do innego elementu 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 niszczy nasz wzór:

Wzór retro z zastosowanym efektem rozmycia.

A co, jeśli chcesz zrobić to samo, co chcesz, ale nie chcesz korzystać z JavaScriptu i nie zaprzątać sobie nim głowy? Jest to całkowicie możliwe w nowym interfejsie API.

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 go i poeksperymentuj z parametrami dostępnymi w prezentacji tutaj.

Część 3. Poprawa wydajności

Nowy interfejs Canvas2D API chcieli też zwiększyć wydajność, jeśli jest to możliwe. Dodaliśmy kilka funkcji, które dają programistom większą kontrolę nad stronami internetowymi i umożliwiają płynne wyświetlanie klatek.

Będzie często czytać

Aby odczytać dane pikseli z obszaru roboczego, naciśnij getImageData(). Może działać bardzo wolno. Nowy interfejs API umożliwia jawne oznaczanie obszaru roboczego do odczytu (np. w przypadku efektów generatywnych). Pozwala to zoptymalizować działanie aplikacji i zwiększyć szybkość jej działania pod kątem wielu różnych zastosowań. Ta funkcja jest dostępna w Firefoksie już od jakiegoś czasu i w końcu dodajemy ją do specyfikacji kanwy.

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

Utrata kontekstu

Czas na uszczęśliwienie smutnych kart. Jeśli w obszarze roboczym zabraknie pamięci GPU lub wystąpi inna katastrofa w obszarze roboczym, możesz teraz otrzymywać wywołanie zwrotne i w razie potrzeby tworzyć ponownie:

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

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

Jeśli chcesz dowiedzieć się więcej o kontekście i utracie kontekstu na płótnie, na stronie wiki WhatWG znajdziesz dokładne wyjaśnienie.

Podsumowanie

Niezależnie od tego, czy dopiero zaczynasz korzystać z Canvas, korzystasz z niej od lat czy nie używasz jej od lat, chcę pokazać Ci, jak ją odświeżyć. To nasze nowe interfejsy API.

Podziękowania

Baner powitalny autorstwa: Sandie Clarke na stronie Unsplash.