# html-in-canvas **Repository Path**: flyleap/html-in-canvas ## Basic Information - **Project Name**: html-in-canvas - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-29 - **Last Updated**: 2026-04-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HTML-in-Canvas This is a proposal for using 2D and 3D `` to customize the rendering of HTML content. ## Status This is a living explainer which is continuously updated as we receive feedback. The APIs described here are implemented behind a flag in Chromium and can be enabled with `chrome://flags/#canvas-draw-element`. ## Motivation There is no web API to easily render complex layouts of text and other content into a ``. As a result, ``-based content suffers in accessibility, internationalization, performance, and quality. ### Use cases * **Styled, Laid Out Content in Canvas.** There’s a strong need for better styled text support in Canvas. Examples include chart components (legend, axes, etc.), rich content boxes in creative tools, and in-game menus. * **Accessibility Improvements.** There is currently no guarantee that the canvas fallback content used for `` accessibility always matches the rendered content, and such fallback content can be hard to generate. With this API, elements drawn into the canvas will match their corresponding canvas fallback. * **Composing HTML Elements with Effects.** A limited set of CSS effects, such as filters, backdrop-filter, and mix-blend-mode are already available, but there is a desire to use general WebGL shaders with HTML. * **HTML Rendering in a 3D Context.** 3D aspects of sites and games need to render rich 2D content into surfaces within a 3D scene. * **Media Export.** There's a need to export HTML content as images or video. ## Proposed solution The solution introduces three main primitives: an attribute to opt-in canvas elements, methods to draw child elements into the canvas, and an event which fires to handle updates. ### 1. The `layoutsubtree` attribute The `layoutsubtree` attribute on a `` element opts in canvas descendants to layout and participate in hit testing. It causes the direct children of the `` to have a stacking context, become a containing block for all descendants, and have paint containment. Canvas element children behave as if they are visible, but their rendering is not visible to the user unless and until they are explicitly drawn into the canvas via a call to `drawElementImage()` (see below). ### 2. `drawElementImage` (and WebGL/WebGPU equivalents) The `drawElementImage()` method draws a child of the canvas into the canvas, and returns a transform that can be applied to `element.style.transform` to align its DOM location with its drawn location. A snapshot of the rendering of all children of the canvas is recorded just prior to the `paint` event. When called during the `paint` event, `drawElementImage()` will draw the child as it would appear in the current frame. When called outside the `paint` event, the previous frame's snapshot is used. An exception is thrown if `drawElementImage()` is called with a child before an initial snapshot has been recorded. **Requirements & Constraints:** * `layoutsubtree` must be specified on the `` in the most recent rendering update. * The `element` must be a direct child of the `` in the most recent rendering update. * The `element` must have generated boxes (i.e., not `display: none`) in the most recent rendering update. * **Transforms:** The canvas's current transformation matrix is applied when drawing into the canvas. CSS transforms on the source `element` are **ignored** for drawing (but continue to affect hit testing/accessibility, see below). * **Clipping:** Overflowing content (both layout and ink overflow) is clipped to the element's border box. * **Sizing:** The optional `width`/`height` arguments specify a destination rect in canvas coordinates. If omitted, the `width`/`height` arguments default to sizing the element so that it has the same on-screen size and proportion in canvas coordinates as it does outside the canvas. **WebGL/WebGPU Support:** Similar methods are added for 3D contexts: `WebGLRenderingContext.texElementImage2D` and `copyElementImageToTexture`. ### 3. The `paint` event A `paint` event is added to `canvas` elements and fires if the rendering of any canvas children has changed. This event fires just after intersection observer steps have run during [update-the-rendering](https://html.spec.whatwg.org/#update-the-rendering). The event contains a list of the canvas children which have changed. Because CSS transforms on canvas children are ignored for rendering, changing the transform does not cause the `paint` event to fire in the next frame. Canvas drawing commands made in the `paint` event will appear in the current frame, but DOM changes made in the `paint` event will not show up until the subsequent frame. To support application patterns which update every frame, a new `requestPaint()` function is added which will cause the `paint` event to fire once, even if no children have changed (analagous to `requestAnimationFrame()`). ### 4. `captureElementImage` To support `OffscreenCanvas` in workers, a snapshot of an element can be captured as an `ElementImage` snapshot using `canvas.captureElementImage(element)`. These objects can be transferred to a worker and drawn to an `OffscreenCanvas`. ### Synchronization Browser features like hit testing, intersection observer, and accessibility rely on an element's DOM location. To ensure these work, the element's `transform` property should be updated so that the DOM location matches the drawn location.
Calculating a CSS transform to match a drawn location The general formula for the CSS transform is:
$$T_{\text{origin}}^{-1} \cdot S_{\text{css} \to \text{grid}}^{-1} \cdot T_{\text{draw}} \cdot S_{\text{css} \to \text{grid}} \cdot T_{\text{origin}} $$
Where: * $$T_{\text{draw}}$$: Transform used to draw the element in the canvas grid coordinate system. For `drawElementImage`, this is $$CTM \cdot T_{(\text{x}, \text{y})} \cdot S_{(\text{destScale})}$$, where $$CTM$$ is the Current Transformation Matrix, $$T_{(\text{x}, \text{y})}$$ is a translation from the x and y arguments, and $$S_{(\text{destScale})}$$ is a scale from the width and height arguments. * $$T_{\text{origin}}$$: Translation matrix of the element's computed `transform-origin`. * $$S_{\text{css} \to \text{grid}}$$: Scaling matrix converting CSS pixels to Canvas Grid pixels.
To assist with synchronization, `drawElementImage()` returns the CSS transform which can be applied to the element to keep its location synchronized. For 3D contexts, the `getElementTransform(element, drawTransform)` helper method is provided which returns the CSS transform, provided a general transformation matrix. The transform used to draw the element on the worker thread needs to be synced back to the DOM, and can simply be `postMessage()`'d back to the main thread if the position is static. If the position is dynamic, an alternative is to calculate the position on the main thread and update `element.style.transform` at the same time that the `ElementImage` objects is sent to the worker thread. ### Basic Example a screenshot showing a form element with a blinking cursor ```html
``` ### OffscreenCanvas Example In this example, `OffscreenCanvas` in a worker is used. The `canvas` child form is captured as an `ElementImage` object in the `paint` event and transferred to the worker for painting. ```html
``` ### IDL changes ```idl partial interface HTMLCanvasElement { [CEReactions, Reflect] attribute boolean layoutSubtree; attribute EventHandler onpaint; void requestPaint(); ElementImage captureElementImage(Element element); DOMMatrix getElementTransform((Element or ElementImage) element, DOMMatrix drawTransform); }; partial interface OffscreenCanvas { DOMMatrix getElementTransform((Element or ElementImage) element, DOMMatrix drawTransform); }; interface mixin CanvasDrawElementImage { DOMMatrix drawElementImage((Element or ElementImage) element, unrestricted double dx, unrestricted double dy); DOMMatrix drawElementImage((Element or ElementImage) element, unrestricted double dx, unrestricted double dy, unrestricted double dwidth, unrestricted double dheight); DOMMatrix drawElementImage((Element or ElementImage) element, unrestricted double sx, unrestricted double sy, unrestricted double swidth, unrestricted double sheight, unrestricted double dx, unrestricted double dy); DOMMatrix drawElementImage((Element or ElementImage) element, unrestricted double sx, unrestricted double sy, unrestricted double swidth, unrestricted double sheight, unrestricted double dx, unrestricted double dy, unrestricted double dwidth, unrestricted double dheight); }; CanvasRenderingContext2D includes CanvasDrawElementImage; OffscreenCanvasRenderingContext2D includes CanvasDrawElementImage; partial interface WebGLRenderingContext { void texElementImage2D(GLenum target, GLint level, GLint internalformat, GLenum format, GLenum type, (Element or ElementImage) element); void texElementImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, (Element or ElementImage) element); void texElementImage2D(GLenum target, GLint level, GLint internalformat, GLfloat sx, GLfloat sy, GLfloat swidth, GLfloat sheight, GLenum format, GLenum type, (Element or ElementImage) element); void texElementImage2D(GLenum target, GLint level, GLint internalformat, GLfloat sx, GLfloat sy, GLfloat swidth, GLfloat sheight, GLsizei width, GLsizei height, GLenum format, GLenum type, (Element or ElementImage) element); }; partial interface GPUQueue { void copyElementImageToTexture((Element or ElementImage) source, GPUImageCopyTextureTagged destination); void copyElementImageToTexture((Element or ElementImage) source, GPUIntegerCoordinate width, GPUIntegerCoordinate height, GPUImageCopyTextureTagged destination); void copyElementImageToTexture((Element or ElementImage) source, float sx, float sy, float swidth, float sheight, GPUImageCopyTextureTagged destination); void copyElementImageToTexture((Element or ElementImage) source, float sx, float sy, float swidth, float sheight, GPUIntegerCoordinate width, GPUIntegerCoordinate height, GPUImageCopyTextureTagged destination); } [Exposed=Window] interface PaintEvent : Event { constructor(DOMString type, optional PaintEventInit eventInitDict); readonly attribute FrozenArray changedElements; }; dictionary PaintEventInit : EventInit { sequence changedElements = []; }; [Exposed=(Window,Worker), Transferable] interface ElementImage { readonly attribute double width; readonly attribute double height; undefined close(); }; ``` ## Demos #### [Live demo](https://wicg.github.io/html-in-canvas/Examples/complex-text.html) ([source](Examples/complex-text.html)) using the `drawElementImage` API to draw rotated complex text. screenshot showing rotated, complex text drawn into canvas #### [Live demo](https://wicg.github.io/html-in-canvas/Examples/pie-chart.html) ([source](Examples/pie-chart.html)) using the `drawElementImage` API to draw a pie chart with multi-line labels. screenshot showing a pie chart #### [Live demo](https://wicg.github.io/html-in-canvas/Examples/webgpu-jelly-slider/) ([source](Examples/webgpu-jelly-slider)) using the WebGPU `copyElementImage` API to draw a div under a jelly slider. screenshot showing a range slider with a jelly effect #### [Live demo](https://wicg.github.io/html-in-canvas/Examples/webGL.html) ([source](Examples/webGL.html)) using the WebGL `texElementImage2D` API to draw HTML onto a 3D cube. screenshot showing html content on a 3D cube A demo of the same thing using an experimental extension of [three.js](https://threejs.org/) is [here](https://raw.githack.com/mrdoob/three.js/htmltexture/examples/webgl_materials_texture_html.html). Further instructions and context are [here](https://github.com/mrdoob/three.js/pull/31233). #### [Live demo](https://wicg.github.io/html-in-canvas/Examples/text-input.html) ([source](Examples/text-input.html)) of interactive content in canvas. screenshot showing a form drawn into canvas ## Privacy-preserving painting The `drawElementImage()` method and any other methods that draw element image snapshots, as well as the paint event, must not reveal any security- or privacy-sensitive information that isn't otherwise observable to author code. Both painting (via canvas pixel readbacks or timing attacks) and invalidation (via `onpaint`) have the potential to leak sensitive information, and this is prevented by excluding sensitive information when painting and invalidating. Sensitive information includes: * Cross-origin data in [embedded content](https://html.spec.whatwg.org/#embedded-content-category) (e.g., `