Por anos, os desenvolvedores da Web tiveram que fazer uma escolha arquitetônica difícil ao criar aplicativos visuais complexos e altamente interativos na Web: usar o DOM pelos recursos semânticos avançados ou renderizar diretamente no elemento <canvas> para um desempenho gráfico de baixo nível?
Com a nova API HTML no 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 uma textura WebGL/WebGPU, mantendo a capacidade de interação e acessibilidade da interface, além de conectá-la aos recursos do seu navegador favorito. Ao combinar HTML com processamento de gráficos 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 básico da interface da Web. Ele oferece soluções de layout de texto prontas para uso, usando conteúdo semanticamente compreendido para criar interfaces avançadas. Isso permite que os usuários realizem operações comuns em páginas da Web sem problemas, coisas que muitas vezes tomamos como garantidas, como destacar texto para copiar ou clicar com o botão direito em uma imagem para salvar. O DOM também se integra a recursos essenciais do navegador: ferramentas de acessibilidade, tradução, localizar 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 acesso de baixo nível para acionar 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 é fundamentalmente uma grade de pixels, o suporte a recursos como texto responsivo exigia uma lógica de interface personalizada complexa, aumentando drasticamente o tamanho do pacote. É importante lembrar que todos os recursos avançados do navegador integrados ao DOM são totalmente 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 no Canvas é a ponte que oferece o melhor dos dois mundos. Ao colocar HTML dentro do elemento <canvas> e sincronizar a transformação, você garante que o conteúdo permaneça totalmente interativo e que todas as integrações do navegador funcionem automaticamente.
Ao deixar o DOM processar sua interface em um elemento <canvas>, você tem:
- 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, cópia/colagem e clique com o botão direito do mouse em texto:os usuários podem destacar texto nas cenas 3D ou clicar com o botão direito do mouse nos menus de contexto de forma nativa.
- Seleção, cópia/colagem e clique com o botão direito do mouse em texto:os usuários podem destacar texto nas cenas 3D ou clicar com o botão direito do mouse nos 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 da mesma forma que o HTML normal e expô-la a sistemas como leitores de tela.
- Find-in-page::os usuários podem usar o recurso "Encontrar na página" (Ctrl/Cmd+F) para buscar texto, e o navegador vai destacar o conteúdo diretamente nas texturas WebGL.
- Find-in-page::os usuários podem usar o recurso "Encontrar na página" (Ctrl/Cmd+F) para buscar texto, e o navegador vai destacar o conteúdo diretamente nas texturas 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 suas malhas 3D.
- Integração do DevTools:você pode inspecionar o conteúdo da tela, incluindo elementos de interface do 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 libera um potencial incrível em vários domínios:
- Aplicativos grandes baseados em tela:apps da Web pesados, como o Google Docs, o Miro ou o Figma, agora podem renderizar componentes complexos da interface do aplicativo de forma nativa nos espaços de trabalho baseados em tela, melhorando a acessibilidade e reduzindo o peso do pacote.
- Cenas e jogos 3D:sites de marketing, experiências imersivas de WebXR e jogos da Web agora podem colocar interfaces da Web totalmente interativas 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 acontece 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 no 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, inscreva-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>
Redimensionar a grade da tela
Para evitar o desfoque do conteúdo renderizado, ajuste o tamanho da grade da tela para corresponder ao fator de escala 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. É fundamental 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 WebGPU
O WebGPU usa o método copyElementImageToTexture na fila do dispositivo, análogo 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, é preciso atualizar o navegador sobre a localização dele. 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 de eventos, como onde exatamente o usuário clica ou passa o cursor, com onde o elemento é renderizado.
Para o caso de 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 o WebGPU, a localização na tela de um elemento depende de como a textura de saída é usada pelo código do shader e não pode ser deduzida do contexto de renderização da tela. No entanto, se o programa de shader usar uma projeção de visualização de modelo típica para desenhar a textura, você poderá usar 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 isso, faça o seguinte:
- Converter a matriz de MVP do WebGL em uma matriz do DOM.
- Normalizar 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, variando 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": ela estica novamente a matemática do espaço da unidade para corresponder às dimensões reais de pixels 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 na ordem:
Viewport * MVP * Normalization.. Combinar as matrizes em uma transformação final produz um "mapa" que informa ao navegador exatamente onde a camada do elemento HTML deve ficar para se alinhar ao desenho 3D. - Aplique a transformação ao elemento HTML. Isso move a camada de elementos HTML para ficar diretamente acima dos pixels renderizados. Isso garante que, quando um usuário clicar em um botão ou selecionar um texto, ele vai acertar 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 na tela.
Three.js
Atualizar matrizes manualmente pode ser cansativo. Por isso, os frameworks já estão entrando nessa onda. 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 é compatível com HTML no 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, desde livros 3D traduzíveis até elementos de interface que refratam por shaders de vidro:
- O livro em 3D: um livro em 3D renderizado em WebGL que usa layout HTML para as páginas. Os usuários podem trocar fontes com CSS. Como é baseada no DOM, a tradução integrada funciona instantaneamente, e os agentes de IA podem extrair o texto com menos complexidade.
- UIs 3D interativas: um controle deslizante gelatinoso do WebGPU que refrata a luz com base em um Modelo 3D subjacente, mas ainda responde aos atributos de etapa
<input type="range">HTML padrão. - Texturas animadas: um outdoor 3D dinâmico renderizando 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 o recurso "Encontrar na página".
Confira a coleção de demonstrações criadas pela comunidade. Se você quiser que sua demonstração de HTML no Canvas apareça nesta 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 de origem cruzada:por motivos de segurança e privacidade, a API não funciona com conteúdo de iframe de origem cruzada.
- Rolagem da linha de execução principal:o HTML na tela é 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 da tela. Os desenvolvedores precisam considerar cuidadosamente as características de desempenho de colocar conteúdo rolável dentro da tela em vez de rolar a tela inteira.
Feedback
Se você estiver testando a API HTML-in-Canvas, queremos saber sua opinião. Você pode se inscrever no teste de origem para ativar o recurso no seu site enquanto ele está 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 no Canvas em Three.js
- Demonstração de HTML na tela em Three.js (em inglês)
- Suporte a HTML no Canvas no PlayCanvas: documentação para desenvolvedores
- Demonstração de HTML-in-Canvas no PlayCanvas
- HTML na tela: explicação
- Orientações da Web moderna para ferramentas de programação de IA para HTML no Canvas
- Demonstrações do Chrome.dev para HTML no Canvas
- Incrível coleção de demonstrações de HTML no Canvas da comunidade