HTML-in-Canvas API: Complete Guide 2026 — Draw DOM in Canvas
Technical Guide · May 21, 2026

HTML-in-Canvas API:
Complete Guide to Drawing DOM in Canvas

Google announced HTML-in-Canvas at Google I/O 2026 — a new web platform API that lets developers render real live DOM elements directly onto an HTML Canvas. Here's everything you need to know, from core primitives to production-ready patterns.

Oleg Maximov May 21, 2026 18 min read

Introduction

For over a decade, web developers have faced an uncomfortable tradeoff: build in the DOM (rich interactivity, accessibility, styling) or build on Canvas (raw pixel control, high performance, 3D graphics). You couldn't have both — at least not elegantly. Solutions like html2canvas and rasterizeHTML approximated screenshots server-side or in the browser, but they were slow, inaccurate, and couldn't handle dynamic content.

At Google I/O 2026 (May 19), Google announced HTML-in-Canvas — a new web platform API that finally bridges this gap. It lets you render real, live DOM elements directly onto an HTML Canvas context, supporting 2D, WebGL, and WebGPU rendering pipelines. Combined with the other 15 Chrome updates announced at I/O, this is one of the most significant additions to the web graphics stack in years. For a full summary of all Chrome announcements, see our Google I/O 2026 Day 2 Recap: Chrome Updates guide.

The API is currently available as an Origin Trial in Chrome 148-150 (stable channel, May 2026). Developers can opt in via the origin trial token or enable chrome://flags/#canvas-draw-element. This guide covers every API primitive with working code examples, browser support timelines, performance strategies, and real-world use cases.

The Problem It Solves

Before HTML-in-Canvas, if you wanted rich interactive HTML — styled forms, complex layouts, live data visualizations — inside a Canvas-based application (game, design tool, video editor), you had three bad options:

The HTML-in-Canvas API solves all of this by giving you a direct pipe from the DOM renderer into your canvas context. It's not a screenshot — it's the actual browser rendering engine painting into your canvas at the pixel level.

Core API Concepts

The API consists of several primitives that work together. All methods live on the HTMLCanvasElement and CanvasRenderingContext2D / WebGLRenderingContext / GPUCanvasContext interfaces.

layoutSubtree(element)

Before you can render a DOM element onto a canvas, the browser needs to compute its layout. layoutSubtree() performs a synchronous layout of the element's subtree, ensuring all dimensions, positions, and computed styles are ready. This is a mandatory first step — calling drawElementImage() or texElementImage2D() without first calling layoutSubtree() produces an empty result.

drawElementImage(element, dx, dy)

Paint a DOM element onto a 2D canvas context. The signature mirrors drawImage() — you can specify destination coordinates and optionally source cropping. The element is rendered at its natural DOM size and painted onto the canvas at the specified coordinates.

texElementImage2D(element, textureUnit)

Upload a DOM element directly as a WebGL or WebGPU texture. This is the most efficient path for 3D rendering — it avoids a CPU round-trip by going directly from the browser compositor to the GPU texture memory.

copyElementImageToTexture(element, texture)

Copy a rendered DOM element into an existing WebGL texture object. Useful for frameworks like Three.js where you manage texture objects directly.

captureElementImage(element)

Return a static ImageBitmap or HTMLImageElement of the rendered element. This is useful for one-time captures where you don't need live updates.

getElementTransform(element)

Returns a DOMMatrix representing the element's current computed transform relative to the canvas coordinate system. Essential for mapping between Canvas coordinates and DOM coordinates, and for applying transforms in 3D scenes.

Paint Event

The paint event fires on the canvas when the HTML-in-Canvas render completes. Listen for it to know when your element has been painted and is ready to use:

canvas.addEventListener('paint', () => {
  // Element has been painted — safe to read back, composite, etc.
});

ElementImage

An ElementImage object represents a cached, renderable snapshot of a DOM element. Create one with canvas.createElementImage(element) for efficient re-rendering without re-laying out the DOM on every frame.

Code Examples

1. 2D Canvas — Render a Styled HTML Form

The simplest case: take a styled HTML form element and paint it onto a 2D canvas. This is useful for game UIs, interactive kiosks, and creative tools:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const form = document.getElementById('loginForm');

// Step 1: Ensure layout is computed
canvas.layoutSubtree(form);

// Step 2: Draw the element onto the canvas
drawElementImage(form, 50, 50);

// Listen for paint completion
canvas.addEventListener('paint', () => {
  console.log('Form rendered on canvas!');
}, { once: true });

2. WebGL — Element as Shader Texture

For real-time graphics applications, use texElementImage2D() to bind a DOM element directly as a WebGL texture:

const gl = canvas.getContext('webgl');
const uiElement = document.getElementById('hud-panel');

// Lay out the element
canvas.layoutSubtree(uiElement);

// Upload directly as texture unit 0
texElementImage2D(uiElement, 0);

// Now available as TEXTURE0 in your shader
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, myTexture);

// The HUD element is now a texture your shader can sample

3. Three.js Integration — THREE.HTMLTexture

For Three.js developers, the API integrates naturally. Create an offscreen element, lay it out, and use it as a material map:

import * as THREE from 'three';

// Create offscreen HTML content
const div = document.createElement('div');
div.innerHTML = '<h2>Hello, 3D!</h2><p>Rendered via HTML-in-Canvas</p>';
div.style.cssText = 'width:300px;height:200px;background:#1a1a2e;color:white;padding:20px;';
document.body.appendChild(div);

// Lay out and capture
canvas.layoutSubtree(div);
const imageBitmap = captureElementImage(div);

// Create Three.js texture
const texture = new THREE.CanvasTexture(imageBitmap);

// Apply to a material
const material = new THREE.MeshStandardMaterial({ map: texture });
const mesh = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), material);
scene.add(mesh);

4. Matrix Math — getElementTransform in Practice

When rendering DOM elements in a 3D scene or transformed canvas context, you need to map coordinates precisely. getElementTransform() returns the full transform matrix:

const transform = canvas.getElementTransform(myElement);
// Returns a DOMMatrix

// Apply transform to a Three.js object
const matrix = new THREE.Matrix4();
matrix.fromArray(transform.toFloat64Array());
threeObject.applyMatrix4(matrix);

// Or use it for hit-testing canvas coordinates back to DOM:
canvas.addEventListener('click', (e) => {
  const inverse = transform.inverse();
  const domPoint = inverse.transformPoint(
    new DOMPoint(e.offsetX, e.offsetY)
  );
  // Check if domPoint.x / domPoint.y falls within the element bounds
});

Element-Scoped View Transitions

Announced alongside HTML-in-Canvas at Google I/O 2026, element-scoped view transitions enable smooth animated transitions between DOM states when those elements are rendered onto a Canvas. This is a critical addition — without it, changing a DOM element's content would cause a jarring visual cut on the Canvas render.

The API works by capturing the previous and next paint states of an element and interpolating between them:

// Start a view transition scoped to the element
const transition = document.startViewTransition({
  update: () => {
    // Change the DOM content
    updateElementContent();
    // Re-layout and re-paint for the canvas
    canvas.layoutSubtree(myElement);
    drawElementImage(myElement, 0, 0);
  }
});

await transition.finished;
console.log('Smooth Canvas transition complete!');

Browser Support Timeline

The HTML-in-Canvas API has been in development for over a year. Here's the current status:

Important: No other browser engine (Firefox, Safari) has committed to implementing HTML-in-Canvas. This is a Chrome-exclusive API for the foreseeable future. Other WebGPU and WebGL innovations continue to be cross-browser — for a broader look at the web AI landscape, see my Chrome Prompt API Guide.

Performance & Best Practices

Getting good performance with HTML-in-Canvas requires understanding where the bottlenecks are:

1. Minimize layoutSubtree() Calls

layoutSubtree() is the most expensive operation — it triggers a full browser layout pass on the element subtree. Call it only when the source DOM actually changes. For animations at 60fps where the DOM stays the same, call it once and use the cached ElementImage for subsequent frames.

2. Listen to the Paint Event

Don't read back canvas pixels or composite immediately after calling drawElementImage(). Wait for the paint event to fire, which signals that the rendering pipeline has completed compositing the element onto the canvas.

3. Always Handle DPR Correctly

The HTML-in-Canvas API renders at the canvas's current pixel dimensions, not CSS dimensions. On high-DPI displays (Retina), you must scale your canvas backing store:

const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
ctx.scale(dpr, dpr);

// Now drawElementImage() renders at native resolution

4. Feature Detection

Always check for API support before using it:

const supportsHtmlInCanvas =
  'layoutSubtree' in HTMLCanvasElement.prototype &&
  'drawElementImage' in CanvasRenderingContext2D.prototype;

if (supportsHtmlInCanvas) {
  // Use the API
} else {
  // Fall back to traditional Canvas rendering or overlaid DOM
}

5. Use ElementImage for Repeated Renders

If you're rendering the same element every frame (e.g., a HUD overlay in a game), create an ElementImage once and reuse it. This avoids re-laying-out the DOM every frame:

const elementImage = canvas.createElementImage(hudElement);
canvas.layoutSubtree(hudElement);

function renderFrame() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  elementImage.draw(ctx, 10, 10);  // Fast path — no DOM layout
  requestAnimationFrame(renderFrame);
}

Real-World Use Cases

The HTML-in-Canvas API opens up several categories of applications that were previously impractical:

Gaming UIs

Game engines embedded in the browser (PlayCanvas, Unity WebGL, custom engines) need rich menus, inventory screens, chat panels, and HUD overlays. Instead of reimplementing every UI widget on Canvas, developers can now build UIs in standard HTML/CSS and render them into the game world. The Three.js integration path makes this especially seamless for WebGL-based game engines.

Interactive Data Visualization

Data viz tools often mix Canvas-rendered charts and graphs with DOM-based tooltips, legends, and controls. With HTML-in-Canvas, tooltips and annotation labels can be true HTML elements rendered directly on the Canvas, preserving their styling and layout while being part of the graphic.

Design Tools

Applications like Figma, Photopea, and Excalidraw render everything on Canvas for performance but need rich text editing, styled inputs, and complex form controls. HTML-in-Canvas allows these tools to embed real DOM elements (text editors, color pickers, dropdowns) at specified Canvas coordinates without the overlay-style positioning hacks.

Video Editing & Motion Graphics

Canvas-based video editors can composite DOM elements (titling cards, lower thirds, credits rolls) directly into their rendering pipeline, using WebGL textures for GPU-accelerated compositing. The element-scoped view transitions enable professional-grade keyframe animations on DOM-based overlay elements.

Interactive Presentations

Presentation frameworks (like Reveal.js alternatives on Canvas) can embed real HTML content — code blocks with syntax highlighting, iframes, complex tables — directly into Canvas-based slides without the "jumpy overlay" feel.

Creative Coding & Generative Art

Artists using Canvas for generative work can now mix DOM-rendered typography, SVG-based elements, and styled HTML widgets into their real-time graphics pipeline, combining the expressive power of Canvas with the layout capabilities of HTML/CSS.

WebGL vs WebGPU Rendering

Both texElementImage2D() and future WebGPU equivalents take different paths:

For most applications in 2026, the WebGL path via texElementImage2D() is the practical choice. WebGPU support is expected to follow in later origin trial iterations.

Migration Strategy

If you have an existing Canvas-based application and want to adopt HTML-in-Canvas, here's a practical migration plan:

  1. Audit your current Canvas rendering. Identify all elements currently drawn with Canvas 2D commands that could be replaced by DOM elements (text labels, buttons, forms).
  2. Build DOM equivalents. Create HTML/CSS versions of those elements in a hidden offscreen container (position:absolute;left:-9999px or a detached div).
  3. Replace Canvas draw calls. Instead of ctx.fillText() or custom button drawing, use layoutSubtree() + drawElementImage().
  4. Add paint event listeners. Ensure your render loop waits for the paint event when compositing frames that depend on HTML-in-Canvas output.
  5. Implement coordinate mapping. Use getElementTransform() to handle Canvas ↔ DOM coordinate translation for interactive elements.
  6. Optimize with ElementImage. For UI elements that don't change every frame, create persistent ElementImage objects and reuse them in the render loop.
  7. Add feature detection and fallback. Keep your old Canvas draw-based rendering as a fallback for browsers without HTML-in-Canvas support.

Limitations & Known Issues

As with any origin-trial API, there are important limitations to be aware of before building production applications:

FAQ

What is the HTML-in-Canvas API?
The HTML-in-Canvas API is a new Chrome web platform feature that lets developers render real live DOM elements directly onto an HTML Canvas. Instead of screenshotting or approximating HTML visually, you call layoutSubtree() to prepare an element, then drawElementImage() (2D) or texElementImage2D() (WebGL/WebGPU) to paint it onto your canvas context. The rendered content stays interactive at the source DOM level.
Which browsers support HTML-in-Canvas?
As of May 2026, HTML-in-Canvas is available as an Origin Trial in Chrome 148-150. It was available as a Developer Trial starting in Chrome 138. No other browser engine (Firefox, Safari) has signaled intent to implement it. You can test it by enabling chrome://flags/#canvas-draw-element or joining the Origin Trial.
What is the difference between drawElementImage and texElementImage2D?
drawElementImage() renders an element onto a 2D CanvasRenderingContext2D — it produces a raster image of the element at the current paint time. texElementImage2D() uploads the rendered element directly as a WebGL/WebGPU texture, which is far more efficient for 3D rendering pipelines because it avoids a CPU→GPU copy. For Three.js, copyElementImageToTexture() gives you a ready-to-use WebGL texture handle.
Does HTML-in-Canvas work with Three.js?
Yes. You can create a THREE.HTMLTexture from an offscreen element and apply it as a material map to any 3D object. This lets you wrap real HTML UIs around 3D geometries, embed rich forms and charts in 3D scenes, and animate HTML content in 3D space — all without prerendering or screenshot hacks.
How does HTML-in-Canvas relate to element-scoped view transitions?
Element-scoped view transitions, announced alongside HTML-in-Canvas at Google I/O 2026, let you animate state changes on DOM elements that are rendered onto a Canvas. When an element's DOM state changes, the new view transition API can crossfade the previous and current renders on the Canvas, enabling smooth animated transitions for Canvas-rendered content without tearing or flicker.
Is HTML-in-Canvas performant for real-time applications?
Performance depends on usage. layoutSubtree() is the bottleneck — it triggers a full layout of the subtree. For real-time apps like games, call layoutSubtree() only when the source DOM changes and use the paint event to throttle re-renders. Use captureElementImage() for one-time captures and texElementImage2D() for frame-by-frame textures. Always call drawElementImage() at the correct DPR scale to avoid blurry output. At 60fps with simple elements, overhead is minimal.
Can users interact with HTML rendered on a Canvas?
Not directly. The rendered HTML on the Canvas is a rasterized snapshot — there are no pointer events, focus, or scroll on the Canvas pixels. The source element remains in the DOM and handles all interaction. You must manually map Canvas coordinates back to the source element using getElementTransform() and forward pointer events as needed. This is an important architecture consideration for interactive applications.

Should You Adopt HTML-in-Canvas Now?

If you're building a Canvas-based application and need rich HTML UIs inside it, yes, but with caveats. The Origin Trial gives you the opportunity to experiment and provide feedback, and the API is stable enough for prototyping and non-critical UIs. For production applications targeting a broad audience, wait for stable release and verify browser support metrics. Always implement graceful degradation — your Canvas app should still work without HTML-in-Canvas, just with less polished UI.

The combination of HTML-in-Canvas, element-scoped view transitions, and the broader Chrome web platform updates (see our WebMCP guide for the AI-agent side of the platform) makes this an exciting time for web graphics. The gap between "what you can build with the DOM" and "what you can build on Canvas" is finally closing.

Contact

Need Help with Canvas-Based Web Development?

I'm a full-stack web developer specializing in Canvas, WebGL, and high-performance web applications. If you're planning to adopt HTML-in-Canvas or need help with your Canvas-based project, let's talk.