HTML-in-Canvas:让 Canvas 完美渲染 HTML 的 Web 新标准

2 阅读4分钟

项目来源:WICG(Web Platform Incubator Community Group)

GitHubgithub.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 属性 — 开启新世界的大门

<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>

截屏2026-04-11 23.55.13.png 效果:支持多语言、排版格式、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 中!

截屏2026-04-12 00.38.32.png

场景 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);

截屏2026-04-11 23.54.26.png

四、隐私安全保护

这个提案非常重视隐私安全,绘制时会自动过滤敏感信息

🔒 被过滤的敏感信息:

  • ❌ 跨域内容(iframe、图片 URL、clip-path 等)
  • ❌ 系统颜色和主题偏好
  • ❌ 拼写检查标记
  • ❌ 访问过的链接样式
  • ❌ 表单自动填充数据
  • ❌ 子像素抗锯齿渲染

✅ 允许的信息:

  • 页面滚动条样式
  • 表单元素外观
  • 搜索标记(find-in-page)

五、如何体验

🔧 启用开发者试验

  1. 使用 Chrome Canary 138.0.7175.0 及以上版本
  2. 访问 chrome://flags/#canvas-draw-element
  3. 启用该功能
  4. 重启浏览器

Demo 列表

Demo说明
复杂文本wicg.github.io/html-in-can…多语言、旋转、带链接和 SVG
饼图wicg.github.io/html-in-can…图表 + 多行标签
表单交互wicg.github.io/html-in-can…完整的表单控件
WebGL 3Dwicg.github.io/html-in-can…HTML 在 3D 立方体上
WebGPU 果冻滑块wicg.github.io/html-in-can…特效 + HTML

六、技术细节

坐标系转换

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 排版
游戏 UICanvas 自绘直接用 HTML/CSS
3D + HTML贴图方案原生支持
无障碍访问难以保证一致性自动同步
性能主线程渲染支持 Worker

这个提案的出现,意味着 Web 开发者可以在 Canvas 的高性能绘图能力和 HTML 的丰富表达能力之间自由切换,再也不用做痛苦的权衡。

项目地址github.com/WICG/html-i…


Demo 代码基于 WICG 官方示例改编,侵删。