Por anos, os desenvolvedores Web tiveram que fazer uma escolha arquitetônica difícil ao criar aplicativos visuais complexos e altamente interativos na Web. Você deve usar o DOM para os recursos semânticos avançados ou renderizar diretamente no elemento <canvas> para desempenho gráfico de baixo nível?
Com a nova API HTML-in-Canvas experimental, já disponível em teste de origem, você não precisa escolher. Com essa API, é possível desenhar conteúdo DOM diretamente em uma tela 2D ou em uma textura WebGL/WebGPU, mantendo a interface interativa, acessível e conectada aos seus recursos favoritos do navegador. Ao combinar HTML com processamento gráfico de baixo nível, é possível criar experiências que antes eram impossíveis.
DOM x Canvas
Para entender o poder dessa nova API, é útil analisar os pontos fortes relativos do DOM e do Canvas.
O DOM é o elemento principal da interface da Web. Ele oferece soluções de layout de texto prontas para uso, usando conteúdo semanticamente compreendido para criar interfaces ricas. Isso permite que os usuários realizem operações comuns em páginas da Web sem problemas, como destacar texto para copiar ou clicar com o botão direito do mouse em uma imagem para salvá-la. O DOM também se integra a recursos essenciais do navegador: ferramentas de acessibilidade, tradução, localização na página, modo de leitura, extensões, modo escuro, zoom do navegador e preenchimento automático.
Por outro lado, o Canvas (e o WebGL/WebGPU) permite o acesso de baixo nível para controlar uma grade de pixels para gráficos 2D e 3D altamente avançados. Jogos e apps da Web complexos (como o Google Docs ou o Figma) exigem esse acesso de baixo nível e alto desempenho. Como o canvas é basicamente uma grade de pixels, recursos como texto responsivo exigiam uma lógica de interface personalizada complexa, o que aumentava drasticamente o tamanho do pacote. O mais importante é que todos os recursos avançados do navegador integrados ao DOM são completamente interrompidos quando a interface fica presa em uma grade de pixels de tela estática.
As vantagens de trazer o DOM para o Canvas
A API HTML-in-Canvas é a ponte que oferece o melhor dos dois mundos. Ao colocar o HTML dentro do elemento <canvas> e sincronizar a transformação dele, você garante que o conteúdo permaneça totalmente interativo e que todas as integrações do navegador funcionem automaticamente.
Veja o que você ganha ao permitir que o DOM processe sua interface em um elemento <canvas>:
- Layout e formatação de texto: layout e formatação de texto simplificados, incluindo texto multilinha ou bidirecional com estilos CSS aplicados.
- Controles de formulário: controles de formulário expressivos e mais fáceis de usar com várias opções de personalização.
- Seleção de texto, copiar/colar e clique com o botão direito do mouse: os usuários podem destacar texto nas cenas 3D ou clicar com o botão direito do mouse em menus de contexto de forma nativa.
- Acessibilidade: o conteúdo renderizado dentro da tela é exposto à árvore de acessibilidade. Os sistemas de acessibilidade podem analisar a interface como fazem com o HTML normal e expor isso a sistemas como leitores de tela.
- Localizar na página: os usuários podem usar o recurso de localização na página (Ctrl/Cmd+F) para pesquisar texto, e o navegador o destaca diretamente nas texturas do WebGL.
- Indexabilidade e interface com agentes de IA: os rastreadores da Web e os agentes de IA podem indexar e ler o texto renderizado nas suas cenas 2D e 3D sem problemas.
- Integração de extensões: as extensões do navegador funcionam de forma nativa. Por exemplo, uma extensão de substituição de texto atualiza automaticamente o texto renderizado nas malhas 3D.
- Integração com o DevTools: é possível inspecionar o conteúdo do canvas, incluindo elementos da interface WebGL/WebGPU, diretamente no Chrome DevTools. Ajuste um estilo CSS no inspetor e veja a atualização instantânea na textura 3D.
Casos de uso de alto nível
Essa API oferece um potencial incrível em vários domínios:
- Aplicativos grandes baseados em tela: agora, os apps da Web pesados podem renderizar componentes complexos de interface de aplicativos de forma nativa nos espaços de trabalho baseados em tela, melhorando a acessibilidade e reduzindo o peso do pacote.
- Cenas e jogos em 3D: sites de marketing, experiências imersivas de WebXR e jogos de navegador agora podem colocar uma interface da Web totalmente interativa em cenas 3D, como um livro 3D que usa texto DOM real ou um terminal no jogo que oferece suporte nativo para copiar e colar.
Como usar a API
O uso da API ocorre em três fases: configuração da tela, renderização na tela e atualização da transformação CSS para que o navegador saiba onde o elemento está fisicamente na tela.
Pré-requisitos
A API HTML-in-Canvas está em teste de origem no Chrome 148 a 150. Para testar no seu site, use o Chrome Canary 149 ou mais recente com a flag chrome://flags/#canvas-draw-element ativada. Para ativar a API para outros usuários, registre-se no teste de origem.
Etapa 1: configuração básica do Canvas
Primeiro, adicione o atributo layoutsubtree à tag <canvas>. Isso faz com que o navegador reconheça o conteúdo aninhado dentro da tela, preparando-o para ser exibido dentro dela e expondo-o a árvores de acessibilidade.
<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>
Dimensionar a grade da tela
Para evitar que o conteúdo renderizado fique desfocado, dimensione a grade da tela para corresponder ao fator de escalonamento do dispositivo.
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);
Etapa 2: renderização
Para um contexto 2D, use o método drawElementImage. Faça isso dentro do evento paint, que é acionado sempre que o elemento é redesenhado, por exemplo, durante o destaque de texto ou a entrada do usuário. É crucial atualizar a transformação CSS do elemento com o valor de retorno para que a interatividade continue funcionando.
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...
};
Renderizar com WebGL
Para WebGL, use texElementImage2D. Ela funciona de maneira semelhante a texImage2D, mas usa o elemento DOM como origem.
canvas.onpaint = () => {
if (gl.texElementImage2D) {
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
}
};
Renderizar com a WebGPU
A WebGPU usa o método copyElementImageToTexture na fila do dispositivo, de forma análoga a copyExternalImageToTexture:
canvas.onpaint = () => {
root.device.queue.copyElementImageToTexture(
valueElement,
{ texture: targetTexture }
);
};
Etapa 3: atualizar a transformação CSS
Agora que você renderizou o elemento na tela, precisa atualizar o navegador sobre onde ele está localizado. Isso garante a sincronização espacial entre a tela e o layout do DOM. Isso é importante para que o navegador possa mapear corretamente a zona do evento, como onde exatamente o usuário clica ou passa o cursor, com o local em que o elemento é renderizado.
No caso do contexto 2D, aplique a transformação retornada pela chamada de renderização ao .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();
};
Com o WebGL ou a WebGPU, o local de um elemento na tela depende de como a textura de saída é usada pelo código do sombreador e não pode ser deduzido do contexto de renderização do canvas. No entanto, se o programa de sombreador usar uma projeção de visualização de modelo típica para desenhar a textura, use a nova função de conveniência element.getElementTransform() para calcular uma transformação que pode ser usada da mesma forma que o valor de retorno de drawElementImage(). Para facilitar, faça o seguinte:
- Converter a matriz MVP do WebGL em matriz DOM.
- Normaliza o elemento HTML. Os elementos HTML são dimensionados em pixels (por exemplo, 200 px de largura). No entanto, o WebGL geralmente trata os objetos como "quadrados unitários", por exemplo, de 0 a 1. Se você não normalizar, o botão de 200 px vai parecer 200 vezes maior.
- Mapear para a janela de visualização da tela. Essa etapa é a fase de "redimensionamento". Ele estende essa matemática de espaço de unidade para corresponder às dimensões reais de pixel do elemento
<canvas>na tela. Ele também inverte o eixo Y, porque no WebGL, para cima é positivo, mas no CSS, para baixo é positivo. - Calcule a transformação final. Multiplique as matrizes em ordem:
Viewport * MVP * Normalization.. Combiná-las em uma transformação final produz um "mapa" que informa ao navegador exatamente onde essa camada de elemento HTML deve ficar para se alinhar ao desenho 3D. - Aplique a transformação ao elemento HTML. Isso move a camada do elemento HTML para ficar diretamente sobre os pixels renderizados. Isso garante que, quando um usuário clicar em um botão ou selecionar um texto, ele esteja interagindo com o elemento HTML real.
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();
}
}
Suporte a bibliotecas e frameworks
Algumas das bibliotecas mais usadas já oferecem suporte ao recurso HTML-in-Canvas.
Three.js
Atualizar matrizes manualmente pode ser entediante, por isso os frameworks já estão se adaptando. O Three.js tem suporte experimental usando o novo 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
O PlayCanvas também oferece suporte a HTML-in-Canvas usando a API de textura:
// 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();
Demonstrações
Antes de testar as demonstrações, verifique se o ambiente está configurado corretamente.
Há várias demonstrações que servem como referência para usar a API. Já estamos vendo soluções criativas da comunidade, que vão de livros 3D traduzíveis a elementos da interface que se refratam por sombreadores de vidro:
- O livro em 3D: um livro em 3D renderizado em WebGL que usa o layout HTML para as páginas. Os usuários podem trocar fontes com CSS. Como é baseada em DOM, a tradução integrada funciona instantaneamente, e os agentes de IA podem extrair o texto com menos complexidade.
- IUs 3D interativas: um controle deslizante de gelatina da WebGPU que refrata a luz com base em um modelo 3D, mas ainda responde aos atributos de etapa
<input type="range">do HTML padrão. - Texturas animadas: um outdoor 3D dinâmico que renderiza um lápis SVG animado usando o DOM diretamente em uma textura WebGL sem precisar de um loop de animação personalizado.
- Sobreposições refrativas: uma camada de tipografia interativa distorcida por um cursor 3D em movimento, mas totalmente selecionável e pesquisável usando a opção de localizar na página.
Confira a coleção de demonstrações criadas pela comunidade. Se você quiser que sua demonstração de HTML-in-Canvas seja incluída nessa coleção, crie uma solicitação de pull para adicioná-la.
Limitações
Embora seja eficiente, a API tem algumas limitações conscientes:
- Conteúdo entre origens: por motivos de segurança e privacidade, a API não funciona com conteúdo de iframe entre origens.
- Rolagem da linha de execução principal: o HTML-in-canvas é desenhado com JavaScript, o que significa que a rolagem e as animações não podem ser atualizadas de forma independente do JavaScript, como acontece fora do canvas. Os desenvolvedores precisam considerar cuidadosamente as características de desempenho de colocar conteúdo de rolagem dentro da tela em vez de fazer com que toda a tela seja rolada.
Feedback
Se você estiver testando a API HTML-in-Canvas, queremos saber sua opinião. Inscreva-se no teste de origem para ativar o recurso no seu site enquanto ele estiver na fase experimental e nos ajudar a moldar o design da API. Você também pode registrar um problema para enviar feedback.
Recursos
- Suporte a HTML-in-Canvas no Three.js
- Demonstração de HTML-in-Canvas no Three.js
- Suporte a HTML-in-Canvas no PlayCanvas: documentação para desenvolvedores (em inglês)
- Demonstração da HTML-in-Canvas no PlayCanvas
- HTML-in-Canvas: Explicação
- Modern Web Guidance para ferramentas de programação de IA para HTML-in-Canvas
- Demonstrações do Chrome.dev para HTML-in-Canvas
- Coleção incrível de demonstrações de HTML-in-Canvas da comunidade