Od lat twórcy stron internetowych muszą podejmować trudną decyzję architektoniczną podczas tworzenia złożonych, wysoce interaktywnych aplikacji wizualnych w internecie: czy korzystać z DOM ze względu na jego bogate funkcje semantyczne, czy renderować bezpośrednio w elemencie <canvas>, aby uzyskać wydajność grafiki niskiego poziomu?
Dzięki nowemu eksperymentalnemu interfejsowi HTML-in-Canvas API, który jest już dostępny w ramach testowania origin, nie musisz wybierać. Ten interfejs API umożliwia rysowanie treści DOM bezpośrednio na płótnie 2D lub teksturze WebGL/WebGPU przy zachowaniu interaktywności i dostępności interfejsu oraz połączenia z ulubionymi funkcjami przeglądarki. Łącząc HTML z przetwarzaniem grafiki niskiego poziomu, możesz tworzyć funkcje, które wcześniej były niemożliwe.
DOM a Canvas
Aby zrozumieć możliwości tego nowego interfejsu API, warto przyjrzeć się względnym zaletom DOM i Canvas.
DOM to podstawa interfejsu internetowego. Oferuje gotowe rozwiązania do układu tekstu, które wykorzystują treści rozumiane semantycznie do tworzenia bogatych interfejsów. Umożliwia to użytkownikom bezproblemowe wykonywanie typowych operacji na stronach internetowych, takich jak zaznaczanie tekstu do skopiowania czy klikanie prawym przyciskiem myszy obrazu, aby go zapisać. DOM jest też zintegrowany z najważniejszymi funkcjami przeglądarki: narzędziami ułatwień dostępu, tłumaczeniem, wyszukiwaniem na stronie, trybem czytania, rozszerzeniami, trybem ciemnym, powiększeniem przeglądarki i autouzupełnianiem.
Canvas (i WebGL/WebGPU) z kolei umożliwia dostęp niskiego poziomu do siatki pikseli, co pozwala tworzyć zaawansowaną grafikę 2D i 3D. Gry i złożone aplikacje internetowe (takie jak Dokumenty Google czy Figma) wymagają wydajnego dostępu niskiego poziomu. Canvas to w zasadzie siatka pikseli, więc obsługa funkcji takich jak tekst responsywny wymagała złożonej logiki interfejsu, co znacznie zwiększało rozmiar pakietu. Co najważniejsze, wszystkie zaawansowane funkcje przeglądarki zintegrowane z DOM przestają działać, gdy interfejs jest uwięziony w statycznej siatce pikseli.
Zalety przeniesienia DOM do Canvas
Interfejs HTML w Canvas to rozwiązanie, które łączy zalety obu tych technologii. Umieszczając kod HTML w elemencie <canvas> i synchronizując jego przekształcenie, masz pewność, że treść pozostanie w pełni interaktywna, a wszystkie integracje przeglądarki będą działać automatycznie.
Oto korzyści z przekazania DOM obsługi interfejsu w elemencie <canvas>:
- Układ i formatowanie tekstu: uproszczony układ i formatowanie tekstu, w tym tekst wielowierszowy lub dwukierunkowy ze stylami CSS.
- Elementy sterujące formularzem: ekspresyjne i łatwiejsze w użyciu elementy sterujące formularzem z rozbudowanymi opcjami dostosowywania.
- Zaznaczanie tekstu, kopiowanie i wklejanie oraz klikanie prawym przyciskiem myszy: użytkownicy mogą zaznaczać tekst w scenach 3D lub klikać prawym przyciskiem myszy menu kontekstowe.
- Zaznaczanie tekstu, kopiowanie i wklejanie oraz klikanie prawym przyciskiem myszy: użytkownicy mogą zaznaczać tekst w scenach 3D lub klikać prawym przyciskiem myszy menu kontekstowe.
- Ułatwienia dostępu: treści renderowane w obszarze roboczym są udostępniane w drzewie ułatwień dostępu. Systemy ułatwień dostępu mogą analizować interfejs użytkownika tak jak zwykły kod HTML i udostępniać go systemom takim jak czytniki ekranu.
- Find-in-page: użytkownicy mogą używać funkcji znajdowania na stronie (Ctrl/Cmd+F), aby wyszukiwać tekst, a przeglądarka będzie go wyróżniać bezpośrednio w teksturach WebGL.
- Find-in-page: użytkownicy mogą używać funkcji znajdowania na stronie (Ctrl/Cmd+F), aby wyszukiwać tekst, a przeglądarka będzie go wyróżniać bezpośrednio w teksturach WebGL.
- Możliwość indeksowania i interakcji z agentami AI: Roboty skanujące i agenty AI mogą bezproblemowo indeksować i odczytywać tekst renderowany w scenach 2D i 3D.
- Integracja rozszerzeń: rozszerzenia do przeglądarki działają natywnie. Na przykład rozszerzenie zastępujące tekst automatycznie zaktualizuje tekst renderowany na siatkach 3D.
- Integracja z Narzędziami deweloperskimi: możesz sprawdzać zawartość obszaru roboczego, w tym elementy interfejsu WebGL/WebGPU, bezpośrednio w Narzędziach deweloperskich w Chrome. Dostosuj styl CSS w inspektorze i obserwuj, jak natychmiast aktualizuje się na teksturze 3D.
Częste korzystanie
Ten interfejs API otwiera ogromne możliwości w kilku obszarach:
- Aplikacje oparte na dużym obszarze roboczym: zaawansowane aplikacje internetowe, takie jak Dokumenty Google, Miro czy Figma, mogą teraz renderować złożone komponenty interfejsu aplikacji natywnie w swoich obszarach roboczych opartych na obszarze roboczym, co zwiększa dostępność i zmniejsza rozmiar pakietu.
- Sceny i gry 3D: strony marketingowe, immersyjne środowiska WebXR i gry internetowe mogą teraz umieszczać w scenach 3D w pełni interaktywne interfejsy internetowe, np. książkę 3D, która wykorzystuje prawdziwy tekst DOM, lub terminal w grze, który natywnie obsługuje kopiowanie i wklejanie.
Jak korzystać z interfejsu API
Korzystanie z interfejsu API odbywa się w 3 fazach: konfigurowanie obszaru roboczego, renderowanie w obszarze roboczym i aktualizowanie przekształcenia CSS, aby przeglądarka wiedziała, gdzie element znajduje się fizycznie na ekranie.
Wymagania wstępne
Interfejs HTML-in-Canvas jest testowany w ramach wersji próbnej origin w Chrome w wersjach 148–150. Aby przetestować tę funkcję w swojej witrynie, użyj Chrome Canary w wersji 149 lub nowszej z włączoną flagą chrome://flags/#canvas-draw-element. Aby włączyć interfejs API dla innych użytkowników, zarejestruj się w programie Origin Trial.
Krok 1. Podstawowa konfiguracja Canvas
Najpierw dodaj atrybut layoutsubtree do tagu <canvas>. Dzięki temu przeglądarka wie, że w elemencie canvas znajdują się treści, które należy przygotować do wyświetlenia w tym elemencie i udostępnić drzewom ułatwień dostępu.
<canvas id="canvas" style="width: 200px; height: 200px;" layoutsubtree>
<div id="form_element">
<label for="name">Name:</label> <input id="name" type="text">
</div>
</canvas>
Zmień rozmiar siatki obszaru roboczego
Aby uniknąć rozmycia renderowanych treści, dopasuj rozmiar siatki obszaru roboczego do współczynnika skali urządzenia.
const observer = new ResizeObserver(([entry]) => {
const dpc = entry.devicePixelContentBoxSize;
canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio);
canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio);
});
const supportsDevicePixelContentBox =
typeof ResizeObserverEntry !== 'undefined' &&
'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);
Krok 2. Renderowanie
W kontekście 2D użyj metody drawElementImage. Zrób to w zdarzeniu paint, które jest wywoływane za każdym razem, gdy element jest ponownie rysowany, np. podczas wyróżniania tekstu lub danych wejściowych użytkownika. Aby interaktywność nadal działała, musisz zaktualizować przekształcenie CSS elementu za pomocą wartości zwrotnej.
const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Use the transform returned later on...
};
Renderowanie za pomocą WebGL
W przypadku WebGL używasz texElementImage2D. Działa podobnie jak texImage2D, ale jako źródło przyjmuje element DOM.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Renderowanie za pomocą WebGPU
WebGPU używa metody copyElementImageToTexture w kolejce urządzenia, analogicznie do copyExternalImageToTexture:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Krok 3. Zaktualizuj przekształcenie CSS
Po wyrenderowaniu elementu na płótnie musisz poinformować przeglądarkę o jego lokalizacji. Zapewnia to synchronizację przestrzenną między obszarem rysowania a układem DOM. Jest to ważne, aby przeglądarka mogła prawidłowo mapować strefę zdarzenia – czyli miejsce, w którym użytkownik kliknie lub najedzie kursorem – z miejscem, w którym element jest renderowany.
W przypadku kontekstu 2D zastosuj przekształcenie zwrócone przez wywołanie renderowania do elementu .style.transform property:
const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');
canvas.onpaint = () => {
ctx.reset();
// Draw the form element at x:0, y:0
let transform = ctx.drawElementImage(form_element, 0, 0);
// Sync the DOM location with the drawn location
form_element.style.transform = transform.toString();
};
W przypadku WebGL lub WebGPU położenie elementu na ekranie zależy od tego, jak tekstura wyjściowa jest używana przez kod shadera, i nie można go wywnioskować z kontekstu renderowania elementu canvas. Jeśli jednak program cieniujący używa typowego modelu widoku projekcji do rysowania tekstury, możesz użyć nowej funkcji element.getElementTransform(), aby obliczyć przekształcenie, które można wykorzystać w taki sam sposób jak wartość zwracaną przez funkcję drawElementImage(). Aby to ułatwić, wykonaj te czynności:
- Przekształć macierz MVP WebGL w macierz DOM.
- Znormalizuj element HTML. Rozmiary elementów HTML są podawane w pikselach (np. szerokość 200 pikseli). WebGL traktuje jednak obiekty jako „kwadraty jednostkowe”, np. w zakresie od 0 do 1. Jeśli nie przeprowadzisz normalizacji, przycisk o rozmiarze 200 pikseli będzie wyglądać na 200 razy większy.
- Mapowanie na widoczny obszar obszaru roboczego Ten krok to faza „ponownego skalowania”: rozciąga on obliczenia w przestrzeni jednostek, aby dopasować je do rzeczywistych wymiarów w pikselach elementu
<canvas>na ekranie. Odwraca też oś Y, ponieważ w WebGL kierunek w górę jest dodatni, a w CSS kierunek w dół jest dodatni. - Oblicz ostateczną transformację. Pomnóż macierze w odpowiedniej kolejności:
Viewport * MVP * Normalization.Połączenie ich w jedną transformację końcową tworzy „mapę”, która dokładnie określa, gdzie powinna znajdować się warstwa elementu HTML, aby była wyrównana z rysunkiem 3D. - Zastosuj przekształcenie do elementu HTML. Powoduje to przesunięcie warstwy elementu HTML bezpośrednio nad renderowane piksele. Dzięki temu, gdy użytkownik kliknie przycisk lub wybierze tekst, będzie to prawdziwy element HTML.
if (canvas.getElementTransform) {
// 1. Convert WebGL MVP Matrix to DOM Matrix
const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));
// 2. Normalize the HTML element (pixels -> 1x1 unit square)
const width = targetHTMLElement.offsetWidth;
const height = targetHTMLElement.offsetHeight;
const cssToUnitSpace = new DOMMatrix()
.scale(1 / width, -1 / height, 1) // Shrink to unit size and flip Y
.translate(-width / 2, -height / 2); // Center the element
// 3. Map to the canvas viewport
const clipToCanvasViewport = new DOMMatrix()
.translate(canvas.width / 2, canvas.height / 2) // Move origin to center
.scale(canvas.width / 2, -canvas.height / 2, 1); // Stretch to canvas dimensions
// 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
const screenSpaceTransform = clipToCanvasViewport
.multiply(mvpDOM)
.multiply(cssToUnitSpace);
// 5. Apply to the transform
const computedTransform = canvas.getElementTransform(targetHTMLElement, screenSpaceTransform);
if (computedTransform) {
targetHTMLElement.style.transform = computedTransform.toString();
}
}
Obsługa bibliotek i platform
Niektóre popularne biblioteki obsługują już funkcję HTML w obszarze roboczym.
Three.js
Ręczne aktualizowanie macierzy może być żmudne, dlatego platformy już zaczynają je wdrażać. Biblioteka Three.js ma eksperymentalne wsparcie w postaci nowego elementu THREE.HTMLTexture:
const material = new THREE.MeshBasicMaterial();
material.map = new THREE.HTMLTexture(uiElement); // Pass the DOM element
const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
PlayCanvas
PlayCanvas obsługuje też HTML w Canvas za pomocą interfejsu API tekstur:
// Wait for the 'paint' event to set the source
canvas.addEventListener('paint', () => {
htmlTexture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();
// Keep up to date
canvas.addEventListener('paint', onPaintUpload);
const material = new pc.StandardMaterial();
material.diffuseMap = htmlTexture;
material.update();
Prezentacje
Zanim wypróbujesz wersje demonstracyjne, upewnij się, że Twoje środowisko jest prawidłowo skonfigurowane.
Dostępnych jest kilka wersji demonstracyjnych, które mogą służyć jako punkt odniesienia podczas korzystania z interfejsu API. W społeczności widać już kreatywne rozwiązania, od książek 3D z możliwością tłumaczenia po elementy interfejsu, które załamują się przez shadery szkła:
- Książka 3D: książka 3D renderowana w WebGL, która wykorzystuje układ HTML na potrzeby stron. Użytkownicy mogą zamieniać czcionki za pomocą CSS. Wbudowane tłumaczenie działa natychmiast, ponieważ jest oparte na DOM, a agenci AI mogą wyodrębniać tekst z mniejszą złożonością.
- Interaktywne interfejsy 3D: suwak WebGPU, który załamuje światło na podstawie modelu 3D, a jednocześnie reaguje na standardowe atrybuty kroku HTML
<input type="range">. - Animowane tekstury: dynamiczny billboard 3D renderujący animowany ołówek SVG przy użyciu DOM bezpośrednio w teksturze WebGL bez konieczności stosowania niestandardowej pętli animacji.
- Nakładki refrakcyjne: interaktywna warstwa typograficzna zniekształcona przez ruchomy kursor 3D, ale w pełni wybieralna i umożliwiająca wyszukiwanie za pomocą funkcji Znajdź na stronie.
Zapoznaj się z kolekcją wersji demonstracyjnych utworzonych przez społeczność. Jeśli chcesz, aby Twoja wersja demonstracyjna HTML w Canvas została uwzględniona w tej kolekcji, utwórz żądanie pull, aby ją dodać.
Ograniczenia
Ten interfejs API jest bardzo przydatny, ale ma kilka celowych ograniczeń:
- Treści z innych domen: ze względów bezpieczeństwa i prywatności interfejs API nie działa z treściami iframe z innych domen.
- Przewijanie w głównym wątku: HTML w obszarze kreacji jest rysowany za pomocą JavaScriptu, co oznacza, że przewijanie i animacje nie mogą być aktualizowane niezależnie od JavaScriptu, jak to ma miejsce poza obszarem kreacji. Deweloperzy powinni dokładnie rozważyć charakterystykę wydajności umieszczania przewijanej treści w obszarze roboczym w porównaniu z przewijaniem całego obszaru roboczego.
Prześlij opinię
Jeśli testujesz interfejs HTML-in-Canvas API, daj nam znać. Możesz zarejestrować się w testowaniu origin, aby włączyć tę funkcję na swojej stronie, gdy jest ona w fazie eksperymentalnej. Pomoże nam to w dopracowaniu projektu interfejsu API. Możesz też zgłosić problem, aby przesłać opinię.
Zasoby
- Obsługa HTML w obszarze rysowania w Three.js
- Prezentacja HTML-in-Canvas w Three.js
- Obsługa HTML w PlayCanvas: dokumentacja dla deweloperów
- Demo HTML-in-Canvas w PlayCanvas
- HTML-in-Canvas: wyjaśnienie
- Wskazówki dotyczące nowoczesnych technologii internetowych dla narzędzi do kodowania AI w przypadku HTML w obszarze roboczym
- Prezentacje na Chrome.dev dotyczące HTML-in-Canvas
- Świetna kolekcja demonstracyjna HTML w Canvasie od społeczności