Rendering HTML inside a WebGPU canvas offers all the benefits that HTML in a WebGL canvas does, such as applying shader effects to the DOM or integrating interactive HTML into 3D scenes. However, WebGPU offers significant benefits compared to WebGL.
This guide demonstrates how to render HTML into a WebGPU canvas. Learn more about HTML-in-Canvas in the API overview.
Rendering HTML in a WebGPU canvas requires the following steps:
- Prepare your canvas. Declaratively set the right attributes to let the browser know it should render HTML into a canvas. This setup is identical across 2D, WebGL, and WebGPU contexts. Learn how to set your canvas up and how to declare HTML elements to be drawn independently.
- Apply the HTML as a texture:
- Pre-allocate GPU texture storage.
- Upload the DOM snapshot as a texture.
- Synchronize the DOM and the pixels.
Draw an HTML element as a WebGPU texture
In WebGPU, HTML elements are uploaded to the GPU as 2D textures using drawElementImageToTexture().
Pre-allocate texture storage
WebGPU requires explicit memory allocation. Before copying DOM pixels into a texture, you must reserve GPU VRAM using the device.createTexture() method:
const context = canvas.getContext("webgpu");
// Pre-allocate the texture size as needed
// For example: HTML element CSS size × devicePixelRatio
const size = canvas.getElementImageDefaultSize(myElement);
const myElementWidth = Math.ceil(size.width);
const myElementHeight = Math.ceil(size.height);
// Pre-allocate the GPUTexture backing (run once during setup)
const myTexture = device.createTexture({
size: {
width: myElementWidth,
height: myElementHeight,
depthOrArrayLayers: 1, // optional (defaults to 1)
},
format: "rgba8unorm",
usage:
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
});
Draw HTML element image
In WebGPU, the drawElementImageToTexture() method draws the snapshot of the HTML element. The method takes two dictionary objects: source and destination.
source defines the source DOM element to rasterize and optional sub-rectangle clipping coordinates that let you copy only a portion of the element into your texture rather than the entire DOM node:
| Property | Type | Description |
|---|---|---|
source required |
Element or ElementImage |
The target DOM element (or pre-captured ElementImage instance) to draw into the texture. |
sourceX optional (all four or none) |
float | The horizontal X-coordinate offset (in CSS pixels) defining the top-left corner of the source sub-rectangle slice. |
sourceY optional (all four or none) |
float | The vertical Y-coordinate offset (in CSS pixels) defining the top-left corner of the source sub-rectangle slice. |
sourceWidth optional (all four or none) |
float | The width (in CSS pixels) of the source sub-rectangle to crop and rasterize. |
sourceHeight optional (all four or none) |
float | The height (in CSS pixels) of the source sub-rectangle to crop and rasterize. |
destination defines the target GPU texture that receives the rendered DOM snapshot, along with optional destination offsets, mip levels, and copy dimensions to place the pixels into a specific region of the texture:
| Property | Type | Description |
|---|---|---|
texture required |
GPUTexture |
The pre-allocated destination WebGPU texture that will receive the rendered DOM pixel snapshot. |
size optional |
GPUExtent3D |
The width and height (in physical texture pixels, for example, { width, height }) to scale and rasterize the element within the destination texture. |
origin optional |
GPUOrigin3D |
The 3D destination offset (x, y, z) within the texture where the rasterized pixels will be written. Defaults to [0, 0, 0]. |
mipLevel optional |
GPUIntegerCoordinate |
The specific mipmap level of the destination texture to write into. Defaults to 0. |
aspect optional |
GPUTextureAspect |
Specifies which texture aspect to copy into ("all", "stencil-only", or "depth-only"). Defaults to "all". |
colorSpace optional |
PredefinedColorSpace |
The destination color space (for example, "srgb", "display-p3") for color conversion upon upload. Defaults to "srgb". |
premultipliedAlpha optional |
boolean | Whether alpha should be premultiplied into the color channels during upload. Defaults to false. |
Upload the element into your texture inside the paint event by calling the drawElementImageToTexture() method:
canvas.addEventListener("paint", (event) => {
const sourceDict = {
source: myElement,
// Optional sub-rectangle crop in CSS pixels
// (defaults to full element if omitted):
sourceX: 0,
sourceY: 0,
sourceWidth: myElement.offsetWidth,
sourceHeight: myElement.offsetHeight,
};
const destDict = {
texture: myTexture,
size: { width: myElementWidth, height: myElementHeight },
};
// Upload the latest DOM snapshot into the pre-allocated GPUTexture
device.queue.drawElementImageToTexture(sourceDict, destDict);
});
Synchronize geometry
To align DOM hit-testing and accessibility focus rings with the rendered canvas output, construct a DOMMatrix transform and apply it with the updateElementGeometry() method:
// Construct a transform as needed in CSS pixels
// relative to the canvas, for example:
// 1/8 of the canvas width from the left edge, vertically centered.
// scaleX, scaleY: canvas grid pixels per CSS pixel (from resize observer)
const canvasCssWidth = canvas.width / scaleX;
const canvasCssHeight = canvas.height / scaleY;
const elementCssHeight = canvas.getElementImageDefaultSize(myElement).height / scaleY;
const x = canvasCssWidth / 8;
const y = (canvasCssHeight - elementCssHeight) / 2;
const transform = new DOMMatrix().translate(x, y);
// Synchronize the element's DOM hit-testing and accessibility geometry
canvas.updateElementGeometry(myElement, { canvasTransform: transform });
Next steps
- Understand the core technical concepts behind HTML-in-Canvas.
- Learn how to render HTML in a 2D context.
- Learn how to render HTML in a WebGL Canvas.
- Learn about fallback strategies.
- File an issue on GitHub to share your feedback.
- Check out the HTML-in-Canvas demo collection by the community.