项目来源:WICG(Web Platform Incubator Community Group)
GitHub :github.com/WICG/html-i...
一、为什么需要这个提案?
当前的痛点
长期以来,<canvas> 和 HTML 是 Web 开发中的两个平行世界:
Canvas 的局限性:
- 文本渲染能力弱,没有原生的 CSS 排版支持
- 无法直接使用 CSS 动画和过渡效果
- Accessibility(无障碍访问)支持差
- 复杂图表(如图例、坐标轴)开发成本高
开发者的困境:
- 游戏开发者想在 Canvas 里渲染精美的 UI 菜单?
- 3D 场景里需要嵌入富文本标签?
- 图表组件需要高质量的文本渲染?
这些问题过去只能靠 workaround 解决,体验差强人意。
HTML-in-Canvas 的愿景
WICG 提出的这个提案,旨在打破 Canvas 和 DOM 之间的壁垒,让 HTML 元素可以直接渲染到 Canvas 画布上,同时保留 CSS 的全部能力和 DOM 的交互性。
二、核心 API 设计
1. layoutsubtree 属性 --- 开启新世界的大门
xml
<canvas id="myCanvas" layoutsubtree width="800" height="600">
<!-- 这些子元素可以被渲染到 Canvas 上 -->
<div id="ui-panel">
<h2>游戏菜单</h2>
<button>开始游戏</button>
</div>
</canvas>
layoutsubtree 的作用:
- 允许 Canvas 的直接子元素参与布局(layout)
- 创建新的堆叠上下文(stacking context)
- 成为所有后代元素的包含块
- 启用命中测试(hit testing)
2. drawElementImage() --- 绘制 HTML 到 Canvas
ini
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
canvas.onpaint = () => {
// 清除画布
ctx.reset();
// 绘制 HTML 元素到 Canvas
ctx.rotate((15 * Math.PI) / 180); // 旋转
let transform = ctx.drawElementImage(ui_panel, 100, 50);
// 同步 DOM 位置以保持可访问性
ui_panel.style.transform = transform.toString();
};
// 触发首次绘制
canvas.requestPaint();
API 签名:
scss
// 基本用法:绘制到指定位置
ctx.drawElementImage(element, x, y);
// 指定目标尺寸(可缩放)
ctx.drawElementImage(element, x, y, width, height);
// 源区域 + 目标区域(裁剪+缩放)
ctx.drawElementImage(element, sx, sy, swidth, sheight,
dx, dy, dwidth, dheight);
3. paint 事件 --- 自动响应变化
ini
canvas.onpaint = (event) => {
// 当任何子元素渲染可能改变时触发
// event.changedElements 包含变化了的元素列表
ctx.reset();
ctx.drawElementImage(element, 0, 0);
};
// 支持 ResizeObserver 同步尺寸
const observer = new ResizeObserver(([entry]) => {
canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
canvas.height = entry.devicePixelBoxSize[0].blockSize;
});
observer.observe(canvas, { box: 'device-pixel-content-box' });
4. WebGL / WebGPU 支持
ini
// WebGL 中绘制 HTML 到纹理
const gl = canvas.getContext('webgl');
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
gl.UNSIGNED_BYTE, htmlElement);
// WebGPU 中复制 HTML 到纹理
const queue = device.queue;
queue.copyElementImageToTexture(source, destination);
三、实际应用场景
场景 1:复杂文本渲染(图表标签)
xml
<canvas id="chart" width="638" height="318" layoutsubtree>
<div id="label" style="width: 550px;">
Hello from HTML-in-Canvas!
<br>Multi-line, <b>formatted</b>, rotated text with emoji
<br>RTL support: <span dir=rtl>من فارسی</span>
<br>Vertical text: <p style="writing-mode: vertical-rl;">垂直文本</p>
<br>Inline SVG: <svg>...</svg>
</div>
</canvas>
效果:支持多语言、排版格式、SVG、内联图片 --- 这些用 Canvas API 几乎不可能实现!
场景 2:游戏 UI 菜单
xml
<canvas id="game" layoutsubtree>
<div id="menu">
<h1>🚀 星际飞船控制面板</h1>
<label>飞船名称:<input type="text" value="Canvas Voyager"></label>
<input type="checkbox" id="hyperdrive" checked>
<label for="hyperdrive">启动超光速引擎</label>
<input type="range" id="shield" min="0" max="100" value="75">
<button type="submit">发射!</button>
</div>
</canvas>
效果:完整的表单交互(输入框、复选框、滑动条)可以渲染到 Canvas 中!

场景 3:3D 场景中的 HTML 标签
结合 Three.js,可以在 3D 立方体上渲染 HTML 内容:
ini
// Three.js 中使用 HTML 纹理
const texture = new THREE.CanvasTexture(htmlCanvas);
const material = new THREE.MeshBasicMaterial({ map: texture });
const cube = new THREE.Mesh(geometry, material);

四、隐私安全保护
这个提案非常重视隐私安全,绘制时会自动过滤敏感信息:
🔒 被过滤的敏感信息:
- ❌ 跨域内容(iframe、图片 URL、clip-path 等)
- ❌ 系统颜色和主题偏好
- ❌ 拼写检查标记
- ❌ 访问过的链接样式
- ❌ 表单自动填充数据
- ❌ 子像素抗锯齿渲染
✅ 允许的信息:
- 页面滚动条样式
- 表单元素外观
- 搜索标记(find-in-page)
五、如何体验
🔧 启用开发者试验
- 使用 Chrome Canary 138.0.7175.0 及以上版本
- 访问
chrome://flags/#canvas-draw-element - 启用该功能
- 重启浏览器
Demo 列表
| Demo | 说明 |
|---|---|
| 复杂文本(wicg.github.io/html-in-can... | 多语言、旋转、带链接和 SVG |
| 饼图(wicg.github.io/html-in-can... | 图表 + 多行标签 |
| 表单交互(wicg.github.io/html-in-can... | 完整的表单控件 |
| WebGL 3D(wicg.github.io/html-in-can... | HTML 在 3D 立方体上 |
| WebGPU 果冻滑块(wicg.github.io/html-in-can... | 特效 + HTML |
六、技术细节
坐标系转换
drawElementImage 返回的变换矩阵用于同步 DOM 位置:
scss
// 计算公式
T_origin^(-1) · S_css→grid^(-1) · T_draw · S_css→grid · T_origin
// 使用方式
let transform = ctx.drawElementImage(element, x, y);
element.style.transform = transform.toString();
OffscreenCanvas 支持
可以在 Worker 线程中绘制,提高性能:
ini
// 主线程:捕获元素为快照
canvas.onpaint = (event) => {
const elementImage = canvas.captureElementImage(formElement);
worker.postMessage({ elementImage }, [elementImage]);
};
// Worker 线程:在离屏 Canvas 中绘制
self.onmessage = (e) => {
if (e.data.elementImage) {
ctx.drawElementImage(e.data.elementImage, 100, 0);
}
};
七、现状与展望
已实现
- Canvas 2D
drawElementImage() layoutsubtree属性paint事件captureElementImage()支持 OffscreenCanvas- WebGL
texElementImage2D()
进行中
- WebGPU
copyElementImageToTexture() - 更多的边界情况处理
当前限制
- 跨域 iframe 暂不支持
- 需要开启实验性标志
- 交互元素需要手动同步位置
八、总结
HTML-in-Canvas 是 Web 平台的一次重要进化:
| 能力 | 以前 | 现在 |
|---|---|---|
| Canvas 文本 | 简陋的 fillText | 完整的 CSS 排版 |
| 游戏 UI | Canvas 自绘 | 直接用 HTML/CSS |
| 3D + HTML | 贴图方案 | 原生支持 |
| 无障碍访问 | 难以保证一致性 | 自动同步 |
| 性能 | 主线程渲染 | 支持 Worker |
这个提案的出现,意味着 Web 开发者可以在 Canvas 的高性能绘图能力和 HTML 的丰富表达能力之间自由切换,再也不用做痛苦的权衡。
项目地址 :github.com/WICG/html-i...
Demo 代码基于 WICG 官方示例改编,侵删。