HTML-in-Canvas: zagadnienia techniczne

Interfejs HTML-in-Canvas API umożliwia renderowanie interaktywnych, dostępnych poddrzew DOM bezpośrednio w kontekstach Canvas 2D, WebGL lub WebGPU.

W tym przewodniku opisujemy zagadnienia techniczne dotyczące HTML-in-Canvas, które są wspólne dla wszystkich kontekstów Canvas:

Przygotowywanie obszaru roboczego do renderowania kodu HTML

Aby renderować elementy HTML w elemencie <canvas>, skonfiguruj te 2 atrybuty:

  • Ustaw content="drawable" na <canvas>: spowoduje to, że element canvas będzie układać swoje elementy potomne DOM, zamiast traktować je wyłącznie jako ukrytą treść.
  • Ustaw drawable na elementach docelowych: oznacza to element jako niezależny element główny renderowania, który może być renderowany na elemencie canvas.
<canvas id="mainCanvas" width="600" height="400" content="drawable">
  <div id="parentElement" class="card" drawable>
    <h2>Container Title</h2>
    <p>Descriptive text inside the parent element.</p>
    <!-- Nested drawable: excluded from parentElement and drawn independently -->
    <button id="childElement" class="btn" drawable>Action Button</button>
  </div>
</canvas>

Omówienie poddrzewa elementów rysowalnych

Podczas przygotowywania kodu HTML do renderowania w obszarze roboczym musisz zdecydować, które elementy mają być rysowane niezależnie. Każdy element drawable reprezentuje niezależny element główny rysowania i jego poddrzewo, które można narysować.

Poddrzewo rysowalne składa się z elementu z atrybutem drawable i wszystkich jego elementów podrzędnych, z wyłączeniem elementów podrzędnych, które również mają atrybut drawable, na przykład:

<canvas content="drawable">
  <!-- Subtree A (Renders parent container and text) -->
  <div id="parentElement" drawable>
    <h2>Parent Card Heading</h2>
    <p>Card body text content.</p>
    <!-- Subtree B (Excluded from Subtree A and drawn separately) -->
    <button id="nestedChildElement" drawable>
      Clickable Button
    </button>
  </div>
</canvas>
Element Granica renderowania Zachowanie
#parentElement (poddrzewo A) Panel tła, nagłówek i akapit Renderowane podczas wywoływania drawElementImage(parentElement, ...)
#nestedChildElement (poddrzewo B) Powierzchnia przycisku i tekst Wykluczone z poddrzewa #parentElement; renderowane w osobnym wywołaniu drawElementImage(nestedChildElement, ...)
Przycisk drawable jest zagnieżdżony w karcie drawable, a każdy z nich jest rysowany przez własne wywołanie drawElementImage().

Zagnieżdżanie elementów rysowalnych pozwala zachować semantyczne relacje HTML, dając jednocześnie swobodę nakładania, pozycjonowania i animowania elementów podrzędnych niezależnie od siebie na scenie obszaru roboczego.

Zdarzenie paint

Interfejs HTML-in-Canvas API wprowadza zdarzenie paint. Przeglądarka automatycznie wysyła zdarzenie paint do elementu <canvas>, gdy zmienia się układ, tekst lub style CSS w dowolnym elemencie podrzędnym drawable.

canvas.addEventListener("paint", (event) => {
  // Clear the viewport
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Redraw elements individually
  ctx.drawElementImage(parentElement, 20, 20);
  ctx.drawElementImage(childElement, 40, 90);
});

Zdarzenie paint wysyła instancję CanvasPaintEvent, która rozszerza standardowy DOM Event i ma właściwość changedElements zawierającą tylko te elementy podrzędne drawable, które zmieniły się wizualnie (np. zmiany stylu, przesunięcia układu, edycje tekstu lub zmiany stanu aktywności) od ostatniego renderowania.

canvas.addEventListener("paint", (event) => {
  // Array of changed elements
  for (const el of event.changedElements) {
    console.log(`Element #${el.id} visually changed and needs repainting`);
  }
});
Przeglądarka uruchamia renderowanie na elemencie canvas za każdym razem, gdy zmienia się renderowanie elementu potomnego, który można narysować.

Cykl życia ramki

Polecenia rysowania na elemencie canvas wykonywane w ramach zdarzenia paint pojawiają się w bieżącej aktualizacji renderowania, ale modyfikacje DOM wprowadzone w ramach zdarzenia paint są widoczne dopiero w następnej klatce:

canvas.addEventListener("paint", () => {
  // Renders in the CURRENT rendering update:
  ctx.drawElementImage(parentElement, 20, 20);

  // Modifying DOM styles inside paint schedules a new layout cycle;
  // changes will appear in the NEXT frame:
  parentElement.style.backgroundColor = "#e0f2fe";
});

Zagnieżdżone obszary robocze

Jeśli strona zawiera zagnieżdżone elementy <canvas content="drawable">, przeglądarka wysyła zdarzenia paint w odwrotnej kolejności drzewa DOM (elementy podrzędne są uruchamiane przed elementami nadrzędnymi). Dzięki temu elementy podrzędne obszaru roboczego są renderowane, zanim zostaną połączone z elementami nadrzędnymi.

Załóżmy, że w poniższym przykładzie wewnętrzny element nestedElement musi zmienić się wizualnie (np. jego kolor tła CSS zmienia się, gdy użytkownik najeżdża na niego kursorem). Najpierw na obszarze roboczym nestedCanvas jest wywoływane zdarzenie paint. Jego detektor czyści bufor podrzędny i wywołuje funkcję ctx.drawElementImage(nestedElement, ...), aby wyrenderować zaktualizowany stan. Następnie zdarzenie paint jest wywoływane na stronie parentCanvas. Jego detektor rysuje na scenie wrapperElement (który zawiera już zaktualizowany obszar roboczy nestedCanvas).

<!-- Outer parent canvas -->
<canvas id="parentCanvas" width="800" height="600" content="drawable">
  <!-- Wrapper container for nested content -->
  <div id="wrapperElement" drawable>

    <!-- Nested child canvas -->
    <canvas id="nestedCanvas" width="300" height="200" content="drawable">
      <div id="nestedElement" class="badge" drawable>
        Inner Child Content
      </div>
    </canvas>

  </div>
</canvas>

<script>
const parentCanvas = document.getElementById("parentCanvas");
const parentCtx = parentCanvas.getContext("2d");

const nestedCanvas = document.getElementById("nestedCanvas");
const nestedCtx = nestedCanvas.getContext("2d");

const nestedElement = document.getElementById("nestedElement");
const wrapperElement = document.getElementById("wrapperElement");

// ============================================================================
// 1. Child Canvas Listener (Fires FIRST)
// ============================================================================
nestedCanvas.addEventListener("paint", (event) => {
  // Clear and redraw the child canvas with the updated inner element
  nestedCtx.clearRect(0, 0, nestedCanvas.width, nestedCanvas.height);
  nestedCtx.drawElementImage(nestedElement, 10, 10);

  console.log("1. Child nested canvas rendered fresh pixels.");
});

// ============================================================================
// 2. Parent Canvas Listener (Fires SECOND)
// ============================================================================
parentCanvas.addEventListener("paint", (event) => {
  // Clear and redraw the parent canvas scene
  parentCtx.clearRect(0, 0, parentCanvas.width, parentCanvas.height);

  // Draws the wrapper container (including the freshly painted nestedCanvas)
  parentCtx.drawElementImage(wrapperElement, 50, 50);

  console.log("2. Parent canvas composited the complete scene.");
});
</script>
Gdy elementy <canvas content="drawable"> są zagnieżdżone, zdarzenia renderowania są wywoływane w odwrotnej kolejności drzewa: najpierw renderowany jest zagnieżdżony obszar roboczy, dzięki czemu nadrzędny obszar roboczy może narysować aktualne piksele.

Wywołaj zdarzenie paint

Wywołaj requestPaint(), aby zaplanować pojedyncze zdarzenie paint w następnej klatce, nawet jeśli nie zmienił się tekst ani style DOM. Można go używać do renderowania interaktywnych scen z ruchomymi elementami, na przykład:

let animatedX = 0;

canvas.addEventListener("paint", () => {
  animatedX += 1.5; // Move the element horizontally
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawElementImage(staticElement, 20, 20);
  ctx.drawElementImage(movingElement, animatedX, 200);
  // Request canvas paint on each rendering update for smooth animation
  canvas.requestPaint();
});

canvas.requestPaint();

Aktualizowanie geometrii elementu

Gdy rysujesz element HTML na obszarze roboczym, kod rysowania określa, gdzie element się pojawi i jak duży będzie. W przeciwieństwie do zwykłej zawartości HTML pozycji i rozmiaru elementu na ekranie, czyli jego geometrii, nie można określić na podstawie samego obliczonego stylu. Przeglądarka potrzebuje geometrii elementu, aby:

  • wysyłać zdarzenia wejściowe, takie jak kliknięcia, dotknięcia i ruchy wskaźnika, do odpowiedniego elementu; Nazywa się to testem pozycji wskaźnika.
  • Zwracanie dokładnych wyników z zapytań dotyczących układu, takich jak getBoundingClientRect().
  • zgłaszać granice elementu technologiom wspomagającym osoby z niepełnosprawnością, takim jak lupy ekranowe;

W trybie 2D drawElementImage() domyślnie automatycznie aktualizuje geometrię. Argumenty, które do niego przekazujesz, wraz z obliczonym stylem elementu i bieżącym przekształceniem kontekstu, wystarczą do określenia, gdzie element się pojawi.

W kontekstach WebGL i WebGPU przeglądarka nie może określić geometrii. Element HTML jest kopiowany do tekstury, a program cieniujący może następnie zmapować tę teksturę na scenę w dowolny sposób. Aby poinformować przeglądarkę, gdzie pojawia się element, przekaż przekształcenie do updateElementGeometry().

Gdy kod przestanie wywoływać funkcję updateElementGeometry(), przeglądarka nie będzie już wiedzieć, gdzie znajdują się elementy HTML na karcie 3D. Karta się obraca, ale kliknięcia nadal trafiają tam, gdzie była wcześniej.

Aby zaktualizować geometrię, przekaż do metody przekształcenie przestrzenne. Dowiedz się, jak utworzyć przekształcenie w różnych kontekstach:

Zarządzanie kolejnością stosu testu pozycji wskaźnika

Domyślnie za każdym razem, gdy geometria elementu jest aktualizowana, przeglądarka przenosi go na górę stosu interakcji (ostatnio narysowany = najwyższy cel kliknięcia). Możesz kontrolować to zachowanie za pomocą preserveHitTestOrder:

// Updates the backdrop without stealing clicks from foreground elements
canvas.updateElementGeometry(backgroundElement, {
  canvasTransform: customTransform,
  preserveHitTestOrder: true
});
Opcja Zachowanie Przypadek użycia
preserveHitTestOrder: false (domyślnie) Przenosi element na górę stosu testu pozycji wskaźnika. Elementy na pierwszym planie, które powinny być klikane jako pierwsze.
preserveHitTestOrder: true Pozostawia element na bieżącym poziomie priorytetu stosu. Elementy tła, które należy zaktualizować po elementach pierwszego planu.

Resetuj geometrię

Aby cofnąć rysowanie elementu bez usuwania go z DOM, wywołaj canvas.clearElementGeometry(). Spowoduje to zresetowanie przekształcenia obszaru roboczego elementu i oznaczenie go jako poza ekranem w drzewie ułatwień dostępu:

// Remove from canvas hit-testing and mark as offscreen
canvas.clearElementGeometry(childElement);

Rozmiary i jednostki

Gdy rysujesz kod HTML na elemencie canvas, przeglądarka działa jednocześnie w 3 układach współrzędnych: pikselach CSS, pikselach urządzenia i pikselach siatki elementu canvas.

Piksele CSS służą do pomiaru i pozycjonowania treści podczas tworzenia układu. Następnie przeglądarka skaluje je o devicePixelRatio, aby zrasteryzować stronę na piksele urządzenia fizycznego. Ponieważ piksele CSS są jednostką logiczną, pozycje i rozmiary często kończą się jako ułamki piksela urządzenia (np. 10,5 piksela CSS × DPR 1,5 = 15,75 piksela urządzenia). Ekran może wyświetlać tylko całe piksele, więc przeglądarka musi rozwiązać problem z wszystkim, co znajduje się między nimi. Dzieje się tak na 2 sposoby:

  • Przyciąganie: granice pól, obramowania i przesunięcia warstw są zaokrąglane do najbliższego pełnego piksela urządzenia, dzięki czemu pozostają wyraźne.
  • Wygładzanie: w przypadku glifów tekstowych, krzywych i przekształconych krawędzi częściowe pokrycie jest mieszane z sąsiednimi pikselami, dzięki czemu nadal wyglądają gładko i znajdują się we właściwym miejscu.

Element <canvas> ma własną siatkę pikseli określoną przez atrybuty width i height, która jest niezależna od pikseli CSS i pikseli urządzenia. Aby go wyświetlić, przeglądarka ponownie próbkuje bitmapę obszaru roboczego na siatce pikseli fizycznych, łącząc sąsiadujące piksele obszaru roboczego, aby wypełnić każdy piksel urządzenia. Aby dowiedzieć się, jak przeglądarka mapuje piksele CSS, canvas i urządzenia, przeczytaj artykuł Renderowanie z dokładnością co do piksela za pomocą devicePixelContentBox.

Wszystkie elementy HTML drawable są rozmieszczone w pikselach CSS. Treści narysowane na elemencie canvas mogą przejść przez 2 konwersje: z pikseli CSS na siatkę elementu canvas, a następnie z siatki elementu canvas na piksele fizyczne. Każdy krok, który nie jest dokładnie dopasowany, dodaje kolejną rundę mieszania, co może spowodować, że wynik będzie wyglądać na rozmyty. Aby tego uniknąć, dopasuj siatkę obszaru roboczego do siatki pikseli fizycznych. Ma to 2 cele:

  • Spójne renderowanie: jeśli element canvas jest mapowany w stosunku 1:1 na piksele fizyczne, konwersja pikseli CSS na piksele elementu canvas daje taki sam wynik jak konwersja pikseli CSS na piksele fizyczne w przeglądarce w przypadku zwykłego kodu HTML.
  • Brak dodatkowego mieszania: jeśli obszar roboczy jest mapowany w stosunku 1:1 na piksele fizyczne, konwersja na piksele urządzenia nie powoduje rozmycia.

Poniższy obraz przedstawia konwersję pikseli, która powoduje rozmycie treści. Te 3 siatki nie są wyrównane: element canvas ma wymiary 360 × 180 pikseli CSS, a rozmiar obszaru roboczego to 300 × 150 pikseli siatki obszaru roboczego. Przy współczynniku pikseli urządzenia wynoszącym 1, 5 obszar roboczy obejmuje 540 × 270 pikseli urządzenia. Jeden piksel CSS obejmuje 1,5 piksela urządzenia, a jeden piksel obszaru roboczego obejmuje 1,2 piksela CSS, czyli 1,8 piksela urządzenia (panel 1). Po narysowaniu okręgu jego położenie i promień są wyrażone w pikselach obszaru roboczego, a nie w pikselach CSS, więc przeglądarka rysuje go na mapie bitowej obszaru roboczego o wymiarach 300 × 150 i wygładza krawędź (panel 2). Aby wyświetlić obszar roboczy, przeglądarka rozciąga tę bitmapę na 540 × 270 pikseli urządzenia, więc musi przeprowadzić interpolację: każdy piksel urządzenia przyjmuje ważoną kombinację najbliższych pikseli obszaru roboczego, które zawierają już wygładzoną krawędź. Krawędź jest mieszana po raz drugi i rozciąga się na kilka pikseli urządzenia, przez co okrąg wygląda na rozmyty (panel 3). Można tego uniknąć, jeśli siatka obszaru roboczego odpowiada pikselom urządzenia w stosunku 1:1. Przeglądarka wygładzi krawędź tylko raz, bezpośrednio na siatce urządzenia, i wyświetli bitmapę bez ponownego próbkowania, dzięki czemu krawędź pozostanie ostra (panel 4):

Diagram składający się z 4 paneli, który porównuje siatki pikseli dla zakrzywionej krawędzi: 1. Niedopasowane siatki CSS (360 x 180), obszaru roboczego (300 x 150) i urządzenia (540 x 270). 2. Krzywa z wygładzonymi krawędziami na siatce obszaru roboczego o wymiarach 300 x 150. 3. Ta bitmapa została interpolowana na siatkę urządzenia o wymiarach 540 x 270, co spowodowało ponowne wygładzenie i rozmycie krawędzi. 4. Obszar roboczy o wymiarach 540 x 270 pikseli, który odpowiada siatce urządzenia w stosunku 1:1, z jednokrotnym wygładzaniem krawędzi, aby były ostre.
Jak niedopasowane siatki pikseli CSS, canvas i urządzenia powodują podwójny antyaliasing (panele 1–3) w porównaniu z siatką canvas-to-device w stosunku 1:1 (panel 4).

Aby uzyskać wyraźny obraz, siatka obszaru roboczego musi odpowiadać liczbie pikseli urządzenia, które obejmuje obszar roboczy: 540 × 270 w poprzednim przykładzie.

Porównanie karty HTML z tekstem „Hello from the DOM! Ta karta to kod HTML narysowany na elemencie canvas.' Po lewej: obraz narysowany na domyślnej siatce obszaru roboczego o wymiarach 300 x 150, który wygląda na rozmyty. Po prawej: rysunek na siatce o wymiarach 800 x 400, której rozmiar został zmieniony za pomocą ResizeObserver, wygląda ostro.
Ten sam obszar roboczy o wymiarach 400 x 200 pikseli CSS na ekranie o współczynniku DPR 1,5. Po lewej: siatka obszaru roboczego 400 x 200. Po prawej: siatka 600 x 300 dostosowana do DPR.

Zmienianie rozmiaru siatki obszaru roboczego za pomocą obserwatora zmiany rozmiaru

Odpowiedni rozmiar siatki zależy od urządzenia i może się zmieniać, gdy zmienia się układ, gdy użytkownik powiększa stronę lub gdy użytkownik przenosi okno na inny wyświetlacz. Zamiast kodować na stałe rozmiar siatki, pozwól przeglądarce określić, jaki powinien być.

Aby siatka obszaru roboczego była zsynchronizowana z obszarem roboczym na ekranie, zawsze ustawiaj jej rozmiar na ResizeObserver i dostosuj siatkę obszaru roboczego do devicePixelContentBoxSize:

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

Konwertowanie pikseli CSS na współrzędne siatki obszaru roboczego

Moduł obsługi paint często łączy pomiary DOM z wywołaniami rysowania, np. aby ustawić element na podstawie jego rozmiaru. Te 2 typy korzystają z różnych jednostek:

  • Piksele obszaru roboczego opisują siatkę obszaru roboczego, której rozmiar jest określany przez canvas.width i canvas.height. Na przykład parametry dx, dy, dwidth i dheight metod rysowania drawElementImage() lub inne parametry interfejsów API elementu canvas, takie jak fillRect() i translate().
  • Piksele CSS opisują stronę. Pomiary DOM, takie jak offsetWidth i clientWidth, oraz współrzędne zdarzenia wskaźnika, takie jak offsetX, są podawane w pikselach CSS.

Oto przegląd jednostek używanych przez różne parametry interfejsu API:

Wartość Jednostki
canvas.width i canvas.height Piksele siatki obszaru roboczego
Parametry rysunku 2D, w tym dx, dy, dwidth i dheight z drawElementImage() Piksele siatki obszaru roboczego
width i height zwrócone przez: canvas.getElementImageDefaultSize() Piksele siatki obszaru roboczego
Prostokąty źródłowe: sx, sy, swidth i sheight z drawElementImage() i texElementSubImage2D(); sourceX, sourceY, sourceWidth i sourceHeight z drawElementImageToTexture() piksele CSS
Rozmiary tekstur: width i height opcje z texElementSubImage2D(); size z drawElementImageToTexture() Piksele tekstury. Domyślnie rozmiar elementu w pikselach siatki obszaru roboczego, zaokrąglony w górę.
Geometria elementu: getElementTransform() oraz opcje canvasTransform i canvasClip w updateElementGeometry() piksele CSS,
pomiary DOM, takie jak offsetWidth, clientWidth, getBoundingClientRect(), współrzędne zdarzenia wskaźnika i contentBoxSize wpisu obserwatora zmiany rozmiaru; piksele CSS,
devicePixelContentBoxSize wpisu obserwatora zmiany rozmiaru Piksele urządzenia

Jeśli użyjesz nieprawidłowych jednostek, rozmiar treści HTML narysowanych na elemencie canvas może nie spełniać Twoich oczekiwań. Podczas rysowania kodu HTML na elemencie canvas musisz przekonwertować wartość piksela CSS na piksele siatki elementu canvas. Aby to zrobić, oblicz stosunek rozmiaru siatki obszaru roboczego do rozmiaru CSS obszaru roboczego: canvas.width / cssContentWidth w poziomie i canvas.height / cssContentHeight w pionie. Obserwator zmiany rozmiaru to wygodne miejsce do obliczenia współczynnika w ten sposób:

// The number of canvas grid pixels per CSS pixel
let scaleX = 1;
let scaleY = 1;

const observer = new ResizeObserver(([entry]) => {
  const dpc = entry.devicePixelContentBoxSize;
  const cssContentWidth = entry.contentRect.width;
  const cssContentHeight = entry.contentRect.height;

  // Best practice: make the canvas grid match the physical pixel grid
  if (dpc) {
    canvas.width = dpc[0].inlineSize;
    canvas.height = dpc[0].blockSize;
  } else {
    canvas.width = Math.round(cssContentWidth * window.devicePixelRatio);
    canvas.height = Math.round(cssContentHeight * window.devicePixelRatio);
  }

  scaleX = canvas.width / cssContentWidth;
  scaleY = canvas.height / cssContentHeight;
});

const supportsDevicePixelContentBox =
  typeof ResizeObserverEntry !== 'undefined' &&
  'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);

Następnie pomnóż wartości pikseli CSS przez scaleX i scaleY, zanim przekażesz je do metody rysowania. Aby na przykład wyśrodkować element rysowalny w obszarze roboczym:

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

canvas.onpaint = () => {
  ctx.reset();

  // offsetWidth and offsetHeight are in CSS pixels.
  // Convert them to canvas grid coordinates.
  const width = card.offsetWidth * scaleX;
  const height = card.offsetHeight * scaleY;
  const x = (canvas.width - width) / 2;
  const y = (canvas.height - height) / 2;

  // drawElementImage() takes canvas grid coordinates.
  ctx.drawElementImage(card, x, y);
};

Na koniec, ponieważ drawElementImage() rysuje obramowanie elementu, użyj box-sizing: border-box, aby uwzględnić dopełnienie i obramowanie w width i height karty, dzięki czemu karta będzie rysowana w ustawionym przez Ciebie rozmiarze:

#card {
  /* Make width and height include padding and border */
  box-sizing: border-box;
  width: 200px;
  padding: 16px;
}

Ułatwienia dostępu do elementów HTML w Canvas

Zasadniczo treść HTML elementu <canvas content="drawable"> jest udostępniana na potrzeby ułatwień dostępu w taki sam sposób jak zwykły DOM, co oznacza, że jest reprezentowana w drzewie ułatwień dostępu, chyba że zostanie wyraźnie ukryta za pomocą atrybutów takich jak aria-hidden="true" lub inert. Istnieje jednak jeden kluczowy wyjątek: element drawable, którego wyrenderowana geometria nie została jeszcze ustalona lub zaktualizowana.

Element drawable w elemencie <canvas content="drawable"> nie ma wyrenderowanej geometrii, jeśli:

  • Nie został jeszcze wyrenderowany za pomocą drawElementImage(), który domyślnie automatycznie aktualizuje geometrię.
  • Jego geometria nie została wyraźnie zaktualizowana za pomocą updateElementGeometry().

Jeśli element drawable nie ma wyrenderowanej geometrii, ten element drawable i jego poddrzewo drawable są zgłaszane do drzewa ułatwień dostępu bez informacji o geometrii. Jest to przydatne, ponieważ pozwala na przykład czytnikom ekranu na dostęp do treści semantycznych, a jednocześnie sygnalizuje wizualnym narzędziom wspomagającym (takim jak lupy), aby odfiltrowywały nienarysowane elementy.

Poniższy przykład zachowuje semantykę rysunku, obrazu i podpisu, ale obraz i rysunek są rysowane osobno:

<canvas id="canvas" width="400" height="300" content="drawable">
  <figure id="figure">
    <img drawable id="image" src="..." alt="image alt text" />
    <figcaption drawable id="caption">A caption</figcaption>
  </figure>
</canvas>
<script>
  const ctx = canvas.getContext('2d');

  canvas.onpaint = () => {
    ctx.reset();
    // Note: Because `preserveElementGeometry` defaults to false, the
    // `drawElementImage` call automatically updates geometry, ensuring
    // the image and caption have geometry information for accessibility.
    ctx.drawElementImage(image, 0, 0);
    ctx.drawElementImage(caption, 0, 200);
  };
</script>

Strategie zastępcze

Interfejs HTML-in-Canvas API został wprowadzony w Chrome 157 i może nie być obsługiwany we wszystkich przeglądarkach. Aby mieć pewność, że Twoje rozwiązanie działa bezproblemowo u wszystkich użytkowników, sprawdź, czy przeglądarka obsługuje interfejs API, i w razie potrzeby skorzystaj z alternatywnego rozwiązania:

export function supportsHtmlInCanvas() {
  if (typeof document === 'undefined') return false;
  const canvas = document.createElement('canvas');
  canvas.setAttribute('content', 'drawable');
  return canvas.content === 'drawable';
}

Jeśli interfejs API nie jest obsługiwany, możesz wybrać jedną z tych alternatyw w zależności od celów implementacji:

Dalsze kroki