跳到主要内容

画布中的 HTML

HTML-in-Canvas 可将实时 HTML 和 CSS 内容直接渲染为 GPU 纹理。这样,带样式的文本、交互 UI 面板、表单等 DOM 内容就能显示在 3D 场景表面,同时保留无障碍、国际化和完整 CSS 样式支持。

备注

Chrome 157 及以上版本默认启用 HTML-in-Canvas,其他浏览器尚不支持。完整提案参见 WICG 说明。

浏览器支持​

使用 device.supportsHtmlTextures 检测当前设备是否支持 HTML 元素纹理:

if (device.supportsHtmlTextures) {
// HTML-in-Canvas is available
} else {
// provide a fallback (e.g. DOM overlay or canvas rasterization)
}

所有未实现 HTML-in-Canvas API 的浏览器都会返回 false。应用始终应提供回退路径。

PlayCanvas 的 WebGL2 和 WebGPU 后端均支持 HTML-in-Canvas。

工作原理​

该 API 引入四个主要基础要素:

  1. content="drawable" 属性:在 <canvas> 元素上设置后,其 HTML 子元素将参与布局。浏览器计算子元素布局,但只有显式绘制到画布时才显示它们。

  2. drawable 属性:标记画布中可以绘制到纹理的子元素。

  3. texElementSubImage2D 和 drawElementImageToTexture:分别是将元素渲染内容绘制到纹理的 WebGL 和 WebGPU 方法。将 HTML 元素传给 Texture.setSource() 时,PlayCanvas 会在内部调用它们。

  4. paint 事件:子元素的视觉内容变化时在画布上触发。使用它重新上传纹理,使纹理与 DOM 保持同步。

基本用法:将 HTML 用作纹理​

要将 HTML 元素作为纹理渲染到 3D 对象:

// 1. Enable HTML-in-Canvas on the application canvas
canvas.setAttribute('content', 'drawable');

// 2. Create and style an HTML element, and mark it as drawable
const htmlElement = document.createElement('div');
htmlElement.setAttribute('drawable', '');
htmlElement.style.width = '512px';
htmlElement.style.height = '512px';
htmlElement.innerHTML = '<h1>Hello 3D World</h1>';
canvas.appendChild(htmlElement);

// 3. Create a texture and bind the HTML element as its source
const texture = new Texture(device, {
width: 512,
height: 512,
format: PIXELFORMAT_RGBA8
});

// Wait for the first paint, then set the HTML element as the texture source
canvas.addEventListener('paint', () => {
texture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();

// Re-upload the texture whenever the HTML content changes
canvas.addEventListener('paint', () => {
texture.upload();
});

// 4. Use the texture on a material
const material = new StandardMaterial();
material.diffuseMap = texture;
material.update();

纹理尺寸取自元素边框盒的 CSS 像素尺寸。使用 PIXELFORMAT_RGBA8 或 PIXELFORMAT_SRGBA8 等 8 位 RGBA 格式,因为 WebGL 只能将元素绘制到这些格式。

HTML 元素支持完整 CSS,包括动画、渐变、Flexbox 等,均可逐帧实时渲染到纹理。避免在被绘制元素上使用 backdrop-filter:它背后没有可模糊的内容,Chrome 会转而模糊元素自身内容。

交互用法:命中测试​

要让渲染在 3D 平面上的 HTML 内容支持点击和悬停,必须告诉浏览器元素在画布中的位置,以便进行命中测试。

canvas.updateElementGeometry(element, { canvasTransform }) API 设置从元素边框盒到画布的变换,以画布的 CSS 像素计量,浏览器便会在该处进行命中测试。配置器示例包含可复用的 HtmlSync 类,根据相机和平面实体计算这一变换:

// Create the sync helper (once)
const sync = new HtmlSync(canvas, htmlElement, planeEntity, panelWidth, panelHeight);

// Update every frame to keep the DOM position in sync with the 3D projection
app.on('update', () => {
sync.update(camera.camera);
});

// Clicks and hovers work via standard DOM events
htmlElement.addEventListener('click', (e) => {
const button = e.target.closest('[data-action]');
if (button) {
// handle the click
}
});

由于浏览器原生处理命中测试,:hover CSS 伪类、焦点管理和无障碍功能都可正常工作。

回退策略​

当 device.supportsHtmlTextures 为 false 时,可考虑以下方式:

  • DOM 叠加:将 HTML 面板作为固定定位的 <div> 渲染在画布上方,通过标准 DOM 事件处理点击。HTML Texture Configurator 示例采用这种方式。
HTML 纹理配置器
  • Canvas 2D 光栅化:使用 2D 上下文将回退内容绘制到 <canvas> 元素,再将其用作纹理源。HTML Texture 示例采用这种方式。
HTML 纹理

API 参考​