Aktualizacje HTML w obiekcie canvas: ulepszanie interfejsu Web API

W Chrome 150 i 155 wprowadziliśmy kilka zmian w interfejsie HTML-in-Canvas API. Te zmiany zostały wprowadzone w odpowiedzi na opinie społeczności i organizacji normalizacyjnych, aby zwiększyć ergonomię interfejsu API. W tym poście na blogu opisujemy te zmiany i wyjaśniamy, jak zadbać o to, aby Twoja implementacja była z nimi zgodna.

Aktualizacje obejmują zmianę nazw atrybutów i metod, wymaganie jawnej wstępnej alokacji pamięci tekstury oraz aktualizację modeli synchronizacji DOM. Dodatkowo okres próbny HTML-in-Canvas został przedłużony. Poniżej znajdziesz wyjaśnienie, co się zmienia, dlaczego i jak zaktualizować bazę kodu.

Zmiany w Chrome 150

W Chrome 150 zmieniliśmy sygnatury argumentów w WebGL i WebGPU, aby wyeliminować nadmiarowość i dostosować je do nowoczesnych konwencji graficznych.

WebGL

Usunęliśmy z funkcji texElementImage2D zbędne argumenty (level, srcFormat, destType) (więcej informacji znajdziesz w dyskusji na temat WebGL):

// Old implementation (Chrome < 150)
const level = 0;
const internalFormat = gl.RGBA;
const srcFormat = gl.RGBA;
const destType = gl.UNSIGNED_BYTE;

gl.texElementImage2D(
  gl.TEXTURE_2D,
  level,
  internalFormat,
  srcFormat,
  destType,
  element
);

// New implementation (from Chrome 150)
const internalFormat = gl.RGBA8;
gl.texElementImage2D(gl.TEXTURE_2D, internalFormat, element);

WebGPU

Ujednoliciliśmy copyElementImageToTextureparametry w sourceDict i destDict obiektach (zobacz GPUWeb PR#6250):

// Old implementation (Chrome < 150)
GPUQueue.copyElementImageToTexture(
  element,
  width,
  height,
  { texture: texture }
);

// New implementation (from Chrome 150)
const sourceDict = { source: element };
const destDict = {
  destination: { texture: texture },
  width: width,
  height: height
};

GPUQueue.copyElementImageToTexture(sourceDict, destDict);

Zmiany w Chrome 155

W Chrome 155 wprowadzamy dalsze ulepszenia na podstawie opinii programistów. Aktualizujemy nazwy metod, wymagamy jawnej wstępnej alokacji pamięci tekstur, obsługujemy złożone poddrzewa DOM i rozdzielamy synchronizację DOM.

WebGPU

Nazwę metody WebGPU zmieniono z copyElementImageToTexture na drawElementImageToTexture. Oprócz zmiany nazwy rozszerzono współrzędne parametrów (sourceX, sourceWidth), a granice miejsca docelowego zgrupowano w standardowym obiekcie size (GPUExtent3D):

// BEFORE (Chrome 150)
const sourceDict = {
  source: myElement,
  sx: 0,
  sy: 0,
  swidth: 100,
  sheight: 100
};
const destDict = {
  destination: { texture: myTexture },
  width: 300,
  height: 200
};

queue.copyElementImageToTexture(sourceDict, destDict);

// AFTER (Chrome 155+)
const sourceDict = {
  source: myElement,
  sourceX: 0,
  sourceY: 0,
  sourceWidth: 100,
  sourceHeight: 100
};
const destDict = {
  texture: myTexture,
  size: { width: 300, height: 200 }
};

queue.drawElementImageToTexture(sourceDict, destDict);

WebGL

Zmiany w WebGL w przypadku HTML w obszarze rysowania obejmują 3 aspekty:

  • Nazwa metody zmienia się z texElementImage2D na texElementSubImage2D.
  • Zmienia się sygnatura metody, a najważniejsze jest to, że dodaje ona argumenty xoffset i yoffset, które opcjonalnie umożliwiają określenie przesunięcia w teksturze docelowej, w której ma być rysowany element.
  • Metoda nie przydziela już automatycznie pamięci buforowanej dla tekstury docelowej. Pamięć musi zostać jawnie przydzielona przed użyciem interfejsu API. Działa to podobnie jak w przypadku texSubImage2D.
// 1. Capture natural layout size of the element
const elementImage = canvas.captureElementImage(element);

// 2. Pre-allocate texture backing once (or when element resizes)
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA8,
  Math.ceil(elementImage.width),
  Math.ceil(elementImage.height),
  0,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  null // null reserves VRAM without uploading pixels
);

// 3. Upload DOM into pre-allocated VRAM
gl.texElementSubImage2D(
  gl.TEXTURE_2D,
  0,                  // level
  0,                  // xoffset
  0,                  // yoffset
  element             // pass DOM element
);

Atrybuty elementu Canvas i elementy podrzędne, które można rysować

Zmieniamy sposób przygotowywania obszaru roboczego do renderowania kodu HTML.

Zmieniam nazwę layoutsubtree na content="drawable"

Aby zachować zgodność z konwencjami nazewnictwa atrybutów HTML, atrybut logiczny layoutsubtreeelementu <canvas> został zmieniony na content="drawable" (szczegółowe omówienie znajdziesz w WICG Issue #169).

Atrybut drawable dla elementów podrzędnych

Wcześniej renderowane były tylko bezpośrednie elementy podrzędne obszaru roboczego. Aby obsługiwać złożone hierarchie DOM bez spłaszczania znaczników, możesz teraz jawnie dodać atrybut drawable do dowolnego elementu podrzędnego, który chcesz rysować niezależnie:

<!-- BEFORE -->
<canvas layoutsubtree>
  <div id="badge">
    <h2>Player Stats</h2>
    <button id="actionBtn">Equip</button>
  </div>
</canvas>

<!-- AFTER (Chrome 155+) -->
<canvas content="drawable">
  <!-- Subtree A (Body and heading) -->
  <div id="badge" drawable>
    <h2>Player Stats</h2>
    <!-- Subtree B (Excluded from Subtree A; drawn and animated independently) -->
    <button id="actionBtn" drawable>Equip</button>
  </div>
</canvas>

Element z atrybutem drawable obejmuje cały poddrzewo z wyjątkiem zagnieżdżonych elementów podrzędnych, które również określają drawable. Dzięki temu kontenery nadrzędne i elementy podrzędne, takie jak przyciski, mogą być animowane i rysowane w osobnych wywołaniach rysowania.

Synchronizacja testowania kliknięć

Wcześniej deweloperzy musieli przechwytywać wartość zwracaną przez metody rysowania i przekazywać ją do funkcji element.style.transform. Metoda drawElementImage() zwraca teraz wartość null, a synchronizacja zależy od kontekstu.

Konteksty 2D: automatyczna synchronizacja

W przypadku płótna 2D przeglądarka automatycznie synchronizuje granice testu trafienia DOM i pierścienie fokusu czytnika ekranu z narysowanymi współrzędnymi. Możesz usunąć wszystkie ręczne przekształcenia:

// BEFORE
const transform = ctx.drawElementImage(element, x, y);
element.style.transform = transform.toString();

// AFTER (Chrome 155+)
ctx.drawElementImage(element, x, y);
// No style update needed: position & hit-testing sync automatically

Aby ręcznie zarządzać geometrią w 2D, przekaż { preserveElementGeometry: true } do drawElementImage() i zastosuj przekształcenia za pomocą canvas.updateElementGeometry().

Konteksty 3D: jawne updateElementGeometry()

Aby zsynchronizować DOM w kontekstach WebGL lub WebGPU, utwórz DOMMatrix transform i przekaż go do canvas.updateElementGeometry():

canvas.addEventListener('paint', () => {
  // 1. Upload element snapshot to GPU texture
  device.queue.drawElementImageToTexture({ source: element }, { texture });

  // 2. Render 3D scene...
  drawScene();

  // 3. Construct transform matrix as needed
  const canvasTransform = new DOMMatrix().translate(x, y);

  // 4. Update DOM hit-testing and accessibility bounding boxes
  canvas.updateElementGeometry(element, { canvasTransform });
});

Zgodność między wersjami

Jeśli w okresie przejściowym aplikacja musi obsługiwać użytkowników korzystających z różnych wersji Chrome, sprawdź, czy w ich prototypach są dostępne zaktualizowane metody, i odpowiednio dostosuj implementację.

Upewnij się, że dla każdej wersji przeglądarki ustawiasz prawidłowe atrybuty elementu <canvas>:

// ============================================================================
//  Cross-version canvas markup
// ============================================================================
if (!canvas.hasAttribute('content') && !canvas.hasAttribute('layoutsubtree')) {
  if ('content' in HTMLCanvasElement.prototype) {
    // Chrome 155+ (New standardized attribute)
    canvas.setAttribute('content', 'drawable');
  } else {
    // Chrome < 155 (Legacy boolean attribute)
    canvas.setAttribute('layoutsubtree', '');
  }
}

W przypadku kontekstu 2D sprawdź wartość zwracaną przez drawElementImage() i atrybuty prototypu elementu canvas, aby obsługiwać obie wersje:

// ============================================================================
// Cross-version 2D drawing & synchronization
// ============================================================================
function draw2DElement(ctx, element, x, y) {
  // Execute the 2D draw call
  const result = ctx.drawElementImage(element, x, y);

  if (result !== undefined && typeof result?.toString === 'function') {
    // Chrome < 155: drawElementImage returned a DOMMatrix
    // Requires manual transform piping to align DOM hit-test bounds:
    element.style.transform = result.toString();
  } else {
    // Chrome 155+: drawElementImage returns void / undefined
    // The browser automatically aligns DOM positioning, clicks, and a11y bounds!
    // If you previously had element.style.transform logic here, clear it:
    if (element.style.transform) {
      element.style.transform = "";
    }
  }
}

W przypadku WebGPU użyj zgodnej sygnatury i nazwy metody:

// ============================================================================
// WebGPU cross-version support
// ============================================================================
if ('drawElementImageToTexture' in GPUQueue.prototype) {
  // Chrome 155+ (Latest implementation)
  queue.drawElementImageToTexture(
    { source: element, sourceX: 0, sourceY: 0, sourceWidth: w, sourceHeight: h },
    { texture: myTexture, size: { width: w, height: h } }
  );
} else if (typeof queue.copyElementImageToTexture === "function") {
  // Chrome 150 - 154: Try the Chrome 150+ dictionary signature first,
  // then fall back to the Chrome < 150 signature
  try {
    queue.copyElementImageToTexture(
      { source: element },
      { destination: { texture: myTexture }, width: w, height: h }
    );
  } catch (err) {
    // Chrome < 150
    queue.copyElementImageToTexture(element, { texture: myTexture });
  }
}

Sprawdź, czy przeglądarka jest zgodna z WebGL:

// ============================================================================
// WebGL cross-version support
// ============================================================================
if ('texElementSubImage2D' in WebGL2RenderingContext.prototype) {
  // Chrome 155+ (Explicit pre-allocation + sub-image)
  gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, element);
} else if (gl.texElementImage2D && gl.texElementImage2D.length === 3) {
  // Chrome 150-154 (3-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, element);
} else if (gl.texElementImage2D) {
  // Chrome < 150 (6-argument signature)
  gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, element);
}

Rozszerzenie okresu próbnego

Testowanie origin HTML-in-Canvas zostało przedłużone do Chrome 160. Aby nadal oferować użytkownikom funkcję HTML w Canvas, odnów token testowania origin. Więcej informacji znajdziesz w artykule o testach Origin.

Prześlij opinię

Daj nam znać, jak te aktualizacje działają w Twoich aplikacjach, zgłaszając problem w repozytorium WICG na GitHubie. Zasubskrybuj newsletter dla deweloperów, aby być na bieżąco z informacjami o HTML w Canvas.