Canvas 和 DOM 分裂了二十年,Chrome 要把它们缝上了

做 Web 3D 的人,最后经常不是卡在模型、材质、灯光上,而是卡在一个按钮上。

你想在 Three.js 场景里放一个产品配置面板:有标题、有说明、有输入框、有按钮、有下拉菜单,最好还能复制文字、选中文本、支持输入法、支持无障碍。

最简单的做法,是把一个 DOM 面板浮在 Canvas 上面。

这样当然能用,但它看起来永远像“盖在 3D 画面上的网页”,不是真正在空间里。只要涉及遮挡、透视、3D 表面、WebXR 菜单、曲面 UI,DOM overlay 就会变得很别扭。

另一种做法,是把 UI 全部画进 WebGL 或 WebGPU。

视觉上统一了,但代价也很高:文本排版、表单、输入法、复制粘贴、选中、无障碍、浏览器翻译、右键菜单、查找,全都要自己补一遍。

HTML-in-Canvas 想解决的就是这个缝。

它让真正的 HTML 元素可以被绘制进 Canvas、WebGL 纹理或 WebGPU 纹理里,同时尽量保留浏览器原生的交互和语义。

换句话说,3D 场景里的按钮,不再只是看起来像按钮,而是真的可以被浏览器当作按钮处理。

HTML-in-Canvas 的核心流程:真实 HTML 参与布局,再绘制到 Canvas、WebGL 或 WebGPU 纹理,同时由浏览器继续处理交互。

这张图可以先抓住本文的核心:HTML-in-Canvas 不是把网页截成一张死图,而是让 DOM 的视觉结果进入 GPU,同时尽量保留浏览器原生交互。

这不是把网页截图贴上去

HTML-in-Canvas 最容易被误解成“把一个 DOM 截图贴到 Canvas 上”。

如果只是截图,那价值有限。你现在用 html2canvas 之类的方案,也能把 DOM 样子栅格化出来。但那种方式本质上只得到一张图,图里的输入框不能输入,按钮不能 hover,文本也不再是浏览器理解的文本。

HTML-in-Canvas 的关键不是“显示 HTML”,而是把 DOM 的能力带进 Canvas。

Chrome 官方文章里强调的能力包括:

  • 文本布局和 CSS 样式;
  • 表单控件;
  • 文本选择、复制、右键菜单;
  • 可访问性树;
  • find-in-page 页面查找;
  • 浏览器扩展、翻译、DevTools 等集成。

这件事对 Web 3D 很重要。

因为 Web 3D 过去最尴尬的地方,就是 Canvas 负责图形,DOM 负责语义,两边像两个世界。模型、材质、光照都在 Canvas 里,真正可用的 UI 却只能浮在外面。

HTML-in-Canvas 的方向,是让 DOM 还能是 DOM,只是它的视觉结果可以进入图形世界。

它怎么工作

这个 API 的思路可以简化成三步。

第一步,在 <canvas> 上加 layoutsubtree

这会让 canvas 里的子元素参与浏览器布局和 hit testing。它们仍然是 HTML 元素,有自己的尺寸、样式、语义和交互区域。

第二步,把这些子元素绘制进 Canvas 或 GPU 纹理。

2D Canvas 里是 drawElementImage()

WebGL 方向是 texElementImage2D()

WebGPU 方向是 copyElementImageToTexture()

第三步,同步它在屏幕上的位置。

这一步很关键。因为 HTML 元素虽然被画到了 3D 表面上,但浏览器要知道它实际出现在屏幕哪里,才能正确处理点击、hover、focus、输入和无障碍。

所以 API 会返回或计算一个 transform,把 DOM 元素的位置和 Canvas / 3D 场景里的绘制位置对齐。

这也是它和普通“贴图”的差别:图像进入了 GPU,交互仍然尽量交给浏览器原生系统。

Three.js 已经接了

对 Web3D 开发者来说,最值得看的不是 API 文档本身,而是 Three.js 的接入。

Three.js PR #31233 已经在 2026 年 4 月 10 日合并到 dev 分支,标题是 Added HTMLTexture

Three.js PR #31233 中的 HTMLTexture 示例:HTML 内容被贴到 3D 立方体表面。

这张 PR 截图比普通 demo 更适合放进文章:它同时显示了合并状态、HTMLTexture / InteractionManager 的设计说明,以及 HTML 内容进入 3D 物体表面的实际效果。

它新增了两个关键东西。

第一个是 HTMLTexture

它可以把一个 live HTML element 渲染到 3D mesh 的纹理上。示例代码大概是这样:

const element = document.createElement('div');
element.innerHTML = 'Hello <b>world</b>! <input type="text">';
 
const material = new MeshStandardMaterial();
material.map = new HTMLTexture(element);
 
const mesh = new Mesh(geometry, material);
scene.add(mesh);

这意味着你可以用正常 HTML 写一块 UI,再把它贴到 Three.js 物体表面。

第二个是 InteractionManager

它的作用更重要:为带有 HTMLTexture 的 mesh 计算 CSS matrix3d,让浏览器原生处理 hit testing、hover、focus 和 input。

也就是说,它不是用 Three.js raycaster 去模拟一套点击系统,而是尽量把交互还给浏览器。

Three.js 里还加了 WebGL 和 WebGPU 两个示例:

  • webgl_materials_texture_html
  • webgpu_materials_texture_html

这说明这件事已经不是纯概念,有框架开始把它封装成开发者能理解的 API。

PlayCanvas 也在跟进

PlayCanvas 文档里也已经有 HTML-in-Canvas 页面。

它的描述很直接:HTML-in-Canvas 可以把 live HTML 和 CSS 内容作为 WebGL texture 渲染,让 styled text、交互 UI 面板、表单和其他 DOM 内容出现在 3D 场景表面,同时保留 accessibility、internationalization 和 CSS 样式支持。

不过 PlayCanvas 的文档也更保守。

它明确要求通过 device.supportsHtmlTextures 检测支持情况。如果不支持,就要准备 fallback:

  • DOM overlay;
  • Canvas 2D rasterization。

它还写明:目前 PlayCanvas 支持的是 WebGL backend,WebGPU 支持还在等待对应浏览器层 API 可用。

这个细节很重要。

它说明生态已经开始接这套能力,但现在还不是“所有 WebGL / WebGPU 项目马上都能稳定用”的阶段。

真正的应用场景

如果 HTML-in-Canvas 成熟,最先受益的不是普通网页,而是那些 Canvas-heavy 和 3D-heavy 应用。

比如 3D 产品配置器。

现在很多产品配置页面会把 3D 模型放中间,参数面板放旁边。未来参数面板可以直接出现在产品旁边,甚至贴在物体表面,同时仍然是可输入、可复制、可访问的 HTML。

比如 WebXR 菜单。

VR / AR 场景里的设置面板、说明卡片、控制台,不一定要重新写一套 UI 系统。如果 HTML 可以进入 3D 表面,很多网页已有能力就能复用。

比如 3D 书籍、教程、展板。

Chrome 官方 demo 里就提到 3D book 这种方向:书页可以用 HTML 布局,字体、翻译、文本提取都能继续靠浏览器能力处理。

再比如 Figma、Miro、Google Docs 这类大型 Canvas 应用。

它们不是 Web3D,但同样长期面对一个问题:Canvas 性能强,DOM 语义强。HTML-in-Canvas 如果成熟,可能让复杂 UI 组件更自然地进入 Canvas 工作区。

所以这件事不只是 Three.js 的小功能。

它更像浏览器在补一个长期缺口:让 Canvas 不再是语义孤岛。

现在还不能盲目上生产

这篇文章最需要讲清楚的边界是:HTML-in-Canvas 还在实验阶段。

Chrome 官方文章写得很明确:它现在处于 Chrome 148 到 150 的 origin trial。开发者测试需要 Chrome Canary 149 或更新版本,并开启:

chrome://flags/#canvas-draw-element

WICG 仓库也说这是 living explainer,会随着反馈持续变化。

也就是说,它不是稳定 Web 标准,不是所有浏览器都支持,更不是你今天就可以无脑塞进生产环境的东西。

还有几个限制要注意。

第一,跨域内容不会随便被画进去。

WICG 文档里专门有 privacy-preserving painting 的部分。跨域 iframe、跨域图片、被污染的 canvas、visited link、autofill 等敏感信息都要受限制。否则这个 API 很容易变成隐私泄露通道。

第二,滚动和动画不是天然完美。

Chrome 官方提到,HTML-in-Canvas 是由 JavaScript 驱动绘制的。滚动和动画不能像普通 DOM 那样完全独立于 JavaScript 更新。复杂滚动内容放进 Canvas 前,仍然要考虑性能和同步问题。

第三,必须有 fallback。

PlayCanvas 文档里也明确建议:如果 supportsHtmlTextures 不支持,就退回 DOM overlay 或 Canvas 2D rasterization。

所以现在更合理的态度是:可以关注,可以实验,可以为未来项目做技术储备,但不要把它当稳定方案直接押上生产。

这件事的真正意义

HTML-in-Canvas 最有意思的地方,不是多了一个 API 名字。

它代表浏览器正在重新连接两个长期分裂的世界:

一边是 DOM,强在语义、交互、可访问性、文本和浏览器集成。

另一边是 Canvas、WebGL、WebGPU,强在高性能图形、复杂视觉和 3D 空间。

过去做 Web 3D UI,经常要在这两边做取舍。

要么保留 DOM 能力,但 UI 像贴在画面外面。

要么进入 3D 世界,但自己重写一堆浏览器已经解决过的东西。

HTML-in-Canvas 的方向,是让这两个世界可以重叠。

如果它成熟,Three.js 里的 3D 面板、WebXR 菜单、产品配置器、空间官网、游戏内终端,可能都会少掉一层自制 UI 系统。

3D 场景里的按钮,不再只是一个看起来像按钮的贴图。

它可以是真的 HTML。

这才是这件事最值得关注的地方。


参考资料: