OffscreenCanvas – przyśpiesz pracę kanw, korzystając z narzędzia internetowego

Tim Dresser

Canvas to popularny sposób rysowania na ekranie różnego rodzaju grafiki. Jest też punktem wejścia do świata WebGL. Możesz go używać do rysowania kształtów, obrazów, uruchamiania animacji, a nawet wyświetlania i przetwarzania treści wideo. Jest on często używany do tworzenia atrakcyjnych aplikacji internetowych i gier online zawierających wiele multimediów.

Można go skonfigurować za pomocą skryptu, co oznacza, że treści narysowane na płótnie mogą być tworzone programowo, na przykład w JavaScript. Daje to dużą elastyczność.

Jednocześnie w przypadku nowoczesnych witryn wykonanie skryptu jest jednym z najczęstszych źródeł problemów z szybkością wczytywania. Logika i renderowanie płótna odbywają się w tym samym wątku co interakcje z użytkownikiem, dlatego (czasami obciążające) obliczenia związane z animowaniem mogą negatywnie wpływać na rzeczywistą i postrzeganą wydajność aplikacji.

Na szczęście OffscreenCanvas jest odpowiedzią na to zagrożenie.

Obsługa przeglądarek

  • Chrome: 69.
  • Edge: 79.
  • Firefox: 105.
  • Safari: 16.4.

Źródło

Wcześniej funkcje rysowania na płótnie były powiązane z elementem <canvas>, co oznaczało, że zależały bezpośrednio od DOM. Jak sama nazwa wskazuje, element OffscreenCanvas odłącza DOM i interfejs Canvas API, przenosząc go poza ekran.

Dzięki temu oddzieleniu renderowanie OffscreenCanvas jest całkowicie niezależne od DOM i w związku z tym oferuje pewne ulepszenia szybkości w porównaniu ze zwykłym canvasem, ponieważ nie ma synchronizacji między nimi.

Co więcej, można go używać w Web Workerze, nawet jeśli nie ma dostępnego DOM. Umożliwia to różne ciekawe zastosowania.

Używanie OffscreenCanvas w instancjach roboczych

Workers to odpowiednik wątków w internecie. Umożliwiają one wykonywanie zadań w tle.

Przeniesienie części skryptu do wątku roboczego daje aplikacji więcej miejsca na wykonywanie zadań ważnych dla użytkownika na wątku głównym. Bez interfejsu OffscreenCanvas nie można było używać interfejsu Canvas API w workerze, ponieważ nie było dostępnej pamięci DOM.

Element OffscreenCanvas nie zależy od DOM, więc można go używać. W tym przykładzie do obliczenia koloru gradientu w elementach worker użyto metody OffscreenCanvas:

// file: worker.js
function getGradientColor(percent) {
  const canvas = new OffscreenCanvas(100, 1);
  const ctx = canvas.getContext('2d');
  const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
  gradient.addColorStop(0, 'red');
  gradient.addColorStop(1, 'blue');
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, ctx.canvas.width, 1);
  const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
  const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
  return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}

getGradientColor(40);  // rgba(152, 0, 104, 255 )

Odblokowanie wątku głównego

Przeniesienie intensywnych obliczeń do instancji roboczej pozwala zwolnić znaczne zasoby w głównym wątku. Aby odzwierciedlić zwykłe tworzywo w przypadku OffscreenCanvas, użyj metody transferControlToOffscreen. Operacje zastosowane do OffscreenCanvas zostaną automatycznie wyrenderowane na płótnie źródłowym.

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({canvas: offscreen}, [offscreen]);

W tym przykładzie intensywne obliczenia występują podczas zmiany motywu kolorów. Powinny one zająć kilka milisekund nawet na szybkim komputerze stacjonarnym. Możesz uruchomić animacje na wątku głównym lub w procesie. W przypadku wątku głównego nie możesz wchodzić w interakcje z przyciskiem, gdy wykonywane jest wymagające dużo zasobów zadanie – wątek jest zablokowany. W przypadku pracownika nie ma wpływu na szybkość reakcji interfejsu.

Prezentacja

Działa to też w drugą stronę: zajęty wątek główny nie wpływa na animację działającą na wątku roboczym. Dzięki tej funkcji możesz uniknąć zacięcia się obrazu i zapewnić płynną animację pomimo ruchu w głównym wątku, jak pokazano w tym filmie demonstracyjnym.

Prezentacja

W przypadku zwykłego kanwy animacja zatrzymuje się, gdy wątki główne są sztucznie przeciążone, a tego samego typu OffscreenCanvas na podstawie wątku pomocniczego odtwarza się płynnie.

Interfejs OffscreenCanvas API jest ogólnie zgodny ze standardowym elementem Canvas, więc możesz go używać jako stopniowego ulepszenia, także w przypadku niektórych wiodących bibliotek graficznych na rynku.

Możesz na przykład wykryć tę funkcję i jeśli jest dostępna, użyć jej w Three.js, podając opcję canvas w konstrukcji renderowania:

const canvasEl = document.querySelector('canvas');
const canvas =
  'OffscreenCanvas' in window
    ? canvasEl.transferControlToOffscreen()
    : canvasEl;
canvas.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});

Tutaj pojawia się jeden problem: Three.js oczekuje, że canvas ma mieć właściwości style.widthstyle.height. OffscreenCanvas, jako całkowicie odłączony od DOM, nie ma tych informacji, więc musisz je podać samodzielnie, albo przez ich zastąpienie, albo przez podanie logiki, która łączy te wartości z oryginalnymi wymiarami kanwy.

Poniżej znajdziesz instrukcje uruchamiania podstawowej animacji Three.js w procesie roboczym:

Prezentacja

Pamiętaj, że niektóre interfejsy API związane z DOM nie są łatwo dostępne w procesie workera, więc jeśli chcesz korzystać z bardziej zaawansowanych funkcji Three.js, takich jak tekstury, może być konieczne zastosowanie innych rozwiązań. Aby dowiedzieć się, jak zacząć eksperymentować z tymi funkcjami, obejrzyj film z Google I/O 2017.

Jeśli często korzystasz z możliwości graficznych canvasa, funkcja OffscreenCanvas może pozytywnie wpłynąć na wydajność aplikacji. Udostępnianie kontekstów renderowania na potrzeby procesu uruchamiania zwiększa równoległość w aplikacjach internetowych i poprawia wykorzystanie systemów wielordzeniowych.

Dodatkowe materiały