在 WebGL 画布中渲染 HTML,可让您将实时 DOM 子树用作动态 GPU 纹理,将着色器效果应用于实时 DOM,并将交互式 HTML 集成到 3D 场景中。
本指南演示了如何将 HTML 渲染到 WebGL 画布中。如需详细了解 HTML-in-Canvas,请参阅 API 概览。
在 WebGL 画布中呈现 HTML 需要执行以下步骤:
- 准备画布。以声明方式设置正确的属性,让浏览器知道它应该将 HTML 渲染到画布中。此设置在 2D、WebGL 和 WebGPU 上下文中完全相同。了解如何设置画布以及如何声明要独立绘制的 HTML 元素。
- 将 HTML 作为纹理应用:
- 预分配 GPU 纹理存储空间。
- 将 DOM 快照作为纹理上传。
- 同步 DOM 和像素。
将 HTML 元素绘制为 WebGL 纹理
在 WebGL 中,HTML 元素会使用 texElementSubImage2D() 作为 2D 纹理上传到 GPU。
预分配纹理存储空间
与自动管理后备内存的 2D 画布不同,WebGL 上下文需要显式内存管理。
在上传 DOM 像素之前,您必须预先分配 GPU 内存。这样,浏览器就可以执行快速子图片更新,而不会触发昂贵的 GPU 内存重新分配。
使用所选的大小参数预分配缓冲区,并将 srcData 设置为 null,如下所示:
// Only allocate texture backing once
if (!textureBufferAllocated) {
// Pre-allocate texture backing with pre-determined size.
gl.texImage2D(
/* target */ gl.TEXTURE_2D,
/* level */ 0,
/* internalformat */ gl.RGBA8,
/* width */ myElementWidth,
/* height */ myElementHeight,
/* border */ 0,
/* format */ gl.RGBA,
/* type */ gl.UNSIGNED_BYTE,
/* srcData */ null
);
textureBufferAllocated = true;
}
如需使纹理与 HTML 元素的自然布局大小相匹配,请使用 canvas.getElementImageDefaultSize() 检索其默认大小(以画布网格像素为单位):
const { width, height } = canvas.getElementImageDefaultSize(myElement);
// Pre-allocate texture backing. Only do this once, or whenever the element
// changes size.
gl.texImage2D(
/* target */ gl.TEXTURE_2D,
/* level */ 0,
/* internalformat */ gl.RGBA8,
/* width */ Math.ceil(width),
/* height */ Math.ceil(height),
/* border */ 0,
/* format */ gl.RGBA,
/* type */ gl.UNSIGNED_BYTE,
/* srcData */ null
);
将 DOM 快照上传为纹理
下一步是在渲染循环或 paint 监听器中将 HTML 元素上传到纹理中。
canvas.onpaint = () => {
// 1. Pre-allocate texture backing first
// ...
// 2. Upload HTML snapshot into the WebGL texture
gl.texElementSubImage2D(
/* target */ gl.TEXTURE_2D,
/* level */ 0,
/* xoffset */ 0,
/* yoffset */ 0,
/* element */ myElement // or an ElementImage from `captureElementImage()`
);
// ...
};
同步几何图形
与 2D 上下文不同,浏览器无法在 3D 上下文中自动推断纹理在屏幕上的显示位置。您需要手动将 DOM 点击测试和无障碍边界框与 3D 网格对齐。
为此,请构建一个 DOMMatrix,将元素的边框框映射到其渲染的画布像素位置,然后使用 updateElementGeometry() 应用该矩阵:
canvas.addEventListener("paint", () => {
// 1. Upload fresh DOM snapshot to GPU texture
gl.bindTexture(gl.TEXTURE_2D, myTexture);
gl.texElementSubImage2D(gl.TEXTURE_2D, 0, 0, 0, myElement);
// 2. Draw your 3D scene
draw3DScene();
// 3. Construct the CSS-pixel transform mapping the element's
// border box to its drawn position on the canvas as needed
// (for example: scale and translate)
const canvasTransform = new DOMMatrix();
// scale X
canvasTransform.a = canvas.clientWidth / myElement.offsetWidth;
// scale Y
canvasTransform.d = canvas.clientHeight / myElement.offsetHeight;
// translate X (CSS px)
canvasTransform.e = 50;
// translate Y (CSS px)
canvasTransform.f = 100;
// 4. Update DOM position, accessibility focus rings, and pointer hit-testing
canvas.updateElementGeometry(myElement, {
canvasTransform: canvasTransform,
});
});
后续步骤
- 了解 HTML-in-Canvas 背后的核心技术概念。
- 了解如何在 2D 上下文中呈现 HTML。
- 了解如何在 WebGPU 画布中渲染 HTML。
- 了解回退策略。
- 在 GitHub 上提交问题,分享您的反馈。
- 查看社区提供的 HTML-in-Canvas 演示集合。