All posts

HTML-in-Canvas: Chrome draws real DOM into canvas and WebGL

Web platform · 4 min read ·

If you have ever built a canvas app, you know the moment: the graphics are fast, and then someone asks why they cannot select the text, search it with Ctrl+F, or read it with a screen reader. Chrome's HTML-in-Canvas API, now in an origin trial, is an answer to that. It draws real DOM content into a 2D canvas or a WebGL/WebGPU texture, and the content stays interactive and accessible.

The choice it removes

Thomas Nattestad and Natalia Markoborodova frame it as an old trade-off. The DOM gives you text layout, form controls and every browser feature built on top of it: accessibility tools, translate, find-in-page, reader mode, extensions, zoom and autofill. Canvas, WebGL and WebGPU give you a grid of pixels and low-level speed, which is why games and apps like Google Docs or Figma use them. The cost is that text inside the pixel grid loses all of those features, and rebuilding even responsive text by hand makes the bundle bigger.

DOM text, forms screen readers, find canvas pixels, 3D low-level speed HTML-in-Canvas the DOM is drawn into the canvas and stays the DOM
Before, you picked one side. The API draws the DOM side into the canvas side.

What you get

According to the Chrome post, content drawn this way keeps:

  • text layout, including multiline and bidirectional text with CSS applied,
  • real form controls,
  • text selection, copy and paste, and the right-click menu,
  • the accessibility tree, so screen readers read it like normal HTML,
  • find-in-page, with the match highlighted inside a WebGL texture,
  • extensions and DevTools: change a CSS value in the inspector and the 3D texture updates.

That bidirectional line matters to me. Arabic text with an English word in it is the kind of thing canvas apps usually get wrong, and here the browser's own text engine does the work.

How it works, in three steps

First, put the HTML inside the <canvas> and add the layoutsubtree attribute, which tells the browser to lay that content out and expose it to the accessibility tree. Second, draw it in the canvas's paint event, which fires whenever the element needs redrawing, for example during text selection or typing. Third, apply the transform that the draw call returns to the element's style.transform, so clicks and hovers land where the element is drawn. This is the post's 2D example:

<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>
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();
};
1. set up layoutsubtree 2. draw paint event 3. sync style.transform repeat on every paint
Skip step three and the element is drawn in one place while clicks go to another.

For WebGL the call is texElementImage2D, which works like texImage2D but takes a DOM element. For WebGPU it is copyElementImageToTexture on the device queue. In 3D the browser cannot work out where your shader puts the texture, so you build the matrix yourself and pass it to getElementTransform(). Three.js already has experimental support through THREE.HTMLTexture, and PlayCanvas supports it through its texture API, which saves you the matrix work.

How to try it, and the limits

The post says the API is in origin trial in Chrome 148 through 150 and is at an early stage, so details may change. To test locally, use Chrome Canary 149 or later with the chrome://flags/#canvas-draw-element flag on. To turn it on for your visitors, register your site for the origin trial.

The authors name two limits. It does not work with cross-origin iframe content, for security and privacy reasons. And because the content is drawn with JavaScript, scrolling and animations inside the canvas cannot run independently of the main thread the way they do on a normal page, so think before putting a long scrolling list in there.

As general advice: this is for apps that already live in a canvas. A normal page built from HTML and CSS has all of these features already and gains nothing from moving into one.

If your app already draws its UI into a canvas, HTML-in-Canvas is the first way to get real text, forms and accessibility back without rebuilding them. Test it behind feature detection; it is an origin trial, not a standard.

Next step: install Chrome Canary, turn on the flag, and try the 2D example above with one of your own form fields. If it holds up, register for the trial and keep a fallback for every other browser. Source: Chrome for Developers, Introducing the HTML-in-Canvas API origin trial.

Notes from readers

  1. No notes yet. The first one is the bravest.
Building something like this? I can help. Message me on WhatsApp