一、为什么需要这个提案?
当前的痛点
长期以来,<canvas>和 HTML 是 Web 开发中的两个平行世界:
Canvas 的局限性:
- 文本渲染能力弱,没有原生的 CSS 排版支持
- 无法直接使用 CSS 动画和过渡效果
- Accessibility(无障碍访问)支持差
- 复杂图表(如图例、坐标轴)开发成本高
开发者的困境:
- 游戏开发者想在 Canvas 里渲染精美的 UI 菜单?
- 3D 场景里需要嵌入富文本标签?
- 图表组件需要高质量的文本渲染?
这些问题过去只能靠 workaround 解决,体验差强人意。
HTML-in-Canvas 的愿景
WICG 提出的这个提案,旨在打破 Canvas 和 DOM 之间的壁垒,让HTML元素可以直接渲染到 Canvas 画布上,同时保留 CSS 的全部能力和 DOM 的交互性。
二、核心 API 设计
1. layoutsubtree
属性 — 开启新世界的大门
<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
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签名:
// 基本用法:绘制到指定位置 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事件 — 自动响应变化
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 支持
// 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:复杂文本渲染(图表标签)
<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 菜单
<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 内容:
// 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 - 启用该功能
- 重启浏览器
六、技术细节
坐标系转换
drawElementImage返回的变换矩阵用于同步 DOM 位置:
// 计算公式 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 线程中绘制,提高性能:
// 主线程:捕获元素为快照 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 的丰富表达能力之间自由切换,再也不用做痛苦的权衡。