惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
Simon Willison's Weblog
Simon Willison's Weblog
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
F
Fortinet All Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
Last Week in AI
Last Week in AI
月光博客
月光博客
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
U
Unit 42
The Register - Security
The Register - Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
H
Hacker News: Front Page
Recent Announcements
Recent Announcements
C
CXSECURITY Database RSS Feed - CXSecurity.com
Latest news
Latest news
小众软件
小众软件
P
Palo Alto Networks Blog
PCI Perspectives
PCI Perspectives
Security Latest
Security Latest
S
Secure Thoughts
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
Recorded Future
Recorded Future
O
OpenAI News
S
Securelist
云风的 BLOG
云风的 BLOG
H
Help Net Security
T
Troy Hunt's Blog

developer.chrome.com: Blog

A developer toolkit to make your website agent-ready  |  Blog  |  Chrome for Developers Unlock runtime insights: Introducing third-party developer tools for Chrome DevTools for agents  |  Blog  |  Chrome for Developers What's New in WebGPU (Chrome 149-150)  |  Blog  |  Chrome for Developers Join the WebMCP origin trial  |  Blog  |  Chrome for Developers Seamless PWA origin migration: Change domains without losing users  |  Blog  |  Chrome for Developers Chrome 150 beta  |  Blog  |  Chrome for Developers New in Chrome 149  |  Blog  |  Chrome for Developers What's new in DevTools (Chrome 149)  |  Blog  |  Chrome for Developers Build new features using built-in AI in Chrome  |  Blog  |  Chrome for Developers What's new in web extensions: I/O 2026 recap  |  Blog  |  Chrome for Developers New in Chrome at Google I/O 2026  |  Blog  |  Chrome for Developers Modernize authentication with passkeys, digital credentials, and more  |  Blog  |  Chrome for Developers 15 updates from Google I/O 2026: Powering the agentic web with new capabilities, tools, and features in Chrome  |  Blog  |  Chrome for Developers Streamline your AI coding workflow with Chrome DevTools for agents 1.0  |  Blog  |  Chrome for Developers Declarative partial updates  |  Blog  |  Chrome for Developers Gap decorations: Now available in Chromium  |  Blog  |  Chrome for Developers Streamlined sign-in: Immediate UI mode is now available  |  Blog  |  Chrome for Developers Install web apps with the new HTML install element | Blog | Chrome for Developers Chrome 149 beta | Blog | Chrome for Developers New in Chrome 148 | Blog | Chrome for Developers What's new in DevTools (Chrome 148) | Blog | Chrome for Developers Container Timing origin trial | Blog | Chrome for Developers Empower your team with expanded roles in the Developer Dashboard | Blog | Chrome for Developers Localization support for web app manifests | Blog | Chrome for Developers Unlock Structured Clone for Chrome Extension Messaging | Blog | Chrome for Developers What's New in WebGPU (Chrome 147-148) | Blog | Chrome for Developers Final Soft Navigations origin trial starting in Chrome 147 | Blog | Chrome for Developers Connection Allowlists origin trial: Secure your web application's network  |  Blog  |  Chrome for Developers Improved Japanese phonetic name support in Chrome autofill  |  Blog  |  Chrome for Developers Take our course about AI evaluations  |  Blog  |  Chrome for Developers Chrome 148 beta | Blog | Chrome for Developers Chrome Web Store: A smarter, faster appeals process | Blog | Chrome for Developers New in Chrome 147 | Blog | Chrome for Developers What's new in DevTools (Chrome 147) | Blog | Chrome for Developers Chrome 147 enables concurrent and nested view transitions with element-scoped view transitions | Blog | Chrome for Developers Enter video Picture-in-Picture automatically on more sites | Blog | Chrome for Developers When to use WebMCP and MCP | Blog | Chrome for Developers Chrome 147 beta | Blog | Chrome for Developers New in Chrome 146 | Blog | Chrome for Developers What's new in DevTools (Chrome 146) | Blog | Chrome for Developers
Introducing the HTML-in-Canvas API origin trial  |  Blog  |  Chrome for Developers
X · 2026-05-19 · via developer.chrome.com: Blog
Skip to main content

Introducing the HTML-in-Canvas API origin trial

Thomas Nattestad

Natalia Markoborodova

For years, web developers have had to make a tough architectural choice when building complex, highly-interactive visual applications on the web/ Should you lean on the DOM for its rich semantic features or render directly to the <canvas> element for low-level graphics performance?

With the new experimental HTML-in-Canvas API—available now in origin trial—you don't have to choose. This API lets you draw DOM content directly into a 2D canvas or a WebGL/WebGPU texture while keeping the UI interactable, accessible, and hooked up to your favorite browser features. By combining HTML with low-level graphics processing, you can create experiences that were previously impossible.

The DOM versus Canvas

To understand the power of this new API, it helps to look at the relative strengths of both the DOM and the Canvas.

The DOM is the staple of web UI. It offers text layout solutions out of the box, using semantically understood content to create rich interfaces. This lets users perform common operations across web pages seamlessly—things we often take for granted, like highlighting text to copy, or right-clicking an image to save it. The DOM also integrates with essential browser features: accessibility tools, translate, find-in-page, reader mode, extensions, dark mode, browser zoom, and autofill.

Canvas (and WebGL/WebGPU), on the other hand, allows for low-level access to drive a grid of pixels for highly advanced 2D and 3D graphics. Games and complex web apps (like Google Docs or Figma) require this performant, low-level access. Because the canvas is fundamentally a grid of pixels, supporting features like responsive text used to require complex custom UI logic, drastically increasing your bundle size. Crucially, all the powerful browser features integrated into the DOM break completely when the UI is trapped inside a static canvas pixel grid.

The advantages of bringing the DOM to Canvas

The HTML-in-Canvas API is the bridge that gives you the best of both worlds. By placing HTML inside the <canvas> element and synchronizing its transform, you ensure the content remains fully interactive, and that all browser integrations function automatically.

Here's what you get by letting the DOM handle your UI inside a <canvas> element:

  • Text layout and formatting: Simplified text layout and formatting, including multiline or bidirectional text with CSS styles applied.
  • Form controls: Expressive and easier to use form controls with extensive customization options.
  • Text selection, copy/paste, and right-click: Users can highlight text inside your 3D scenes, or right-click context menus natively.
  • Accessibility: Content rendered inside the canvas is exposed to the accessibility tree. Accessibility systems can parse the UI as they do normal HTML, and expose it to systems like screen readers.
  • Find-in-page: Users can use find-in-page (Ctrl/Cmd+F) to search for text, and the browser highlights it directly within your WebGL textures.
  • Indexability and AI agent interfaceable: Web crawlers and AI agents can seamlessly index and read the text rendered into your 2D and 3D scenes.
  • Extension integration: Browser extensions work natively. For example, a text-replacement extension automatically updates the text rendered on your 3D meshes.
  • DevTools integration: You can inspect your canvas content, including for WebGL/WebGPU UI elements directly in Chrome DevTools. Tweak a CSS style in the inspector, and watch it instantly update on the 3D texture!

High-level use cases

This API unlocks incredible potential across several domains:

  • Large canvas-based applications: Heavyweight web apps like Google Docs, Miro, or Figma can now render complex application UI components natively into their canvas-driven workspaces, improving accessibility and reducing bundle weight.
  • 3D scenes and games: Marketing sites, immersive WebXR experiences, and web games can now place fully interactable web UI into 3D scenes—like a 3D book that uses real DOM text, or an in-game terminal that natively supports copying and pasting.

How to use the API

Using the API happens in three phases: Setting up your canvas, rendering into the canvas, and updating the CSS transform so the browser knows where the element physically sits on the screen.

Prerequisites

The HTML-in-Canvas API is in origin trial in Chrome 148 through 150. To test it on your site, use Chrome Canary 149 or later with the chrome://flags/#canvas-draw-element flag enabled. To enable the API for other users, register for the Origin Trial.

Step 1: Basic Canvas setup

First, add the layoutsubtree attribute to your <canvas> tag. This makes the browser aware of the content nested inside the canvas, preparing it to be displayed inside the canvas, and exposing it to accessibility trees.

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

Size the canvas grid

To avoid blurriness of the rendered content, make sure to size the canvas grid to match the device scale factor.

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

Step 2: Rendering

For a 2D context, use the drawElementImage method. Do this inside the paint event, which triggers whenever the element redraws—for example, during text highlighting or user input. It's crucial to update the element's CSS transform with the return value so interactivity continues to work.

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...
};

Render with WebGL

For WebGL, you use texElementImage2D. It functions similar to texImage2D, but takes the DOM element as the source.

canvas.onpaint = () => {
  if (gl.texElementImage2D) {
    gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
  }
};

Render with WebGPU

WebGPU uses the copyElementImageToTexture method on the device queue, analogous to copyExternalImageToTexture:

canvas.onpaint = () => {
  root.device.queue.copyElementImageToTexture(
    valueElement,
    { texture: targetTexture }
  );
};

Step 3: Update the CSS transform

Now that you've rendered the element into the canvas, you have to update the browser on where it's located. This ensures spatial synchronization between the canvas and the DOM's layout. This is important so that the browser can correctly map the event zone—such where exactly the user clicks or hovers—with where the element is rendered.

For the 2D context case, apply the transform returned by the rendering call to the .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();
};

With WebGL or WebGPU, the on-screen location of an element depends on how the output texture is used by shader code, and can't be deduced from the canvas rendering context. However, if your shader program uses a typical model view projection to draw the texture, then you can use the new convenience function element.getElementTransform() to compute a transform that can be used in the same way as the return value from drawElementImage(). To facilitate this, you need to do the following:

  • Convert WebGL MVP Matrix to DOM Matrix.
  • Normalize the HTML element. HTML elements are sized in pixels (for example, 200px wide). WebGL, however, usually treats objects as "unit squares", for example, ranging from 0 to 1. If you don't normalize, your 200px button will look 200 times larger.
  • Map to the canvas viewport. This step is the "rescaling" phase. It stretches that unit-space math back out to match the actual pixel dimensions of your <canvas> element on the screen. It also flips the Y-axis, because in WebGL, up is positive, but in CSS, down is positive.
  • Calculate the final transform. Multiply the matrices in order: Viewport * MVP * Normalization. Combining them into one final transform produces a "map" that tells the browser exactly where that HTML element layer should sit to align with the 3D drawing.
  • Apply the transform to the HTML element. This moves the HTML element layer to sit directly on top of its rendered pixels. This ensures that when a user clicks a button or selects text, they're hitting the real HTML element.
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();
  }
}

Library and framework support

Some of the popular libraries have already shipped support for the HTML-in-Canvas feature.

Three.js

Updating matrixes manually can be tedious, which is why frameworks are already jumping on board. Three.js has experimental support using the new 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

PlayCanvas also supports HTML-in-Canvas using their texture API:

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

Demos

Before trying out the demos, ensure your environment is properly configured.

There are several demos that serve as a reference for using the API. We are already seeing creative solutions from the community, ranging from translatable 3D books to UI elements that refract through glass shaders:

  • The 3D book: A WebGL-rendered 3D book that uses HTML layout for its pages. Users can swap fonts with CSS. Because it's DOM-based, built-in translation works instantly, and AI agents can extract the text with less complexity.
  • Interactive 3D UIs: A WebGPU jelly slider that refracts light based on an underlying 3D model, while still responding to standard HTML <input type="range"> step attributes.
  • Animated textures: A dynamic 3D billboard rendering an animated SVG pencil using the DOM directly into a WebGL texture without needing a custom animation loop.
  • Refractive overlays: An interactive typography layer distorted by a moving 3D cursor, yet fully selectable and searchable using find-in-page.

Check out the collection of demos created by the community. If you'd like your HTML-in-Canvas demo to be featured in this collection, create a pull request to add it.

Limitations

While powerful, the API has a few conscious limitations:

  • Cross-origin content: For security and privacy reasons, the API does not work with cross-origin iframe content.
  • Main thread scrolling: HTML-in-canvas is drawn with JavaScript, which means that scrolling and animations cannot update independently of JavaScript, like they can outside canvas. Developers should carefully consider the performance characteristics of putting scrolling content inside canvas versus having the entire canvas scroll.

Feedback

If you are experimenting with the HTML-in-Canvas API, we want to hear from you! You can sign up for the origin trial to enable the feature on your site while it's in the experimental phase to help us shape the API design. You can also file an issue to provide any feedback.

Resources

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-05-19 UTC.

[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2026-05-19 UTC."],[],[]]