專案來源:WICG(Web Platform Incubator Community Group)
GitHub:github.com/WICG/html-in-canvas
長期以來,<canvas></canvas> 和 HTML 是 Web 開發中的兩個平行世界:
Canvas 的侷限性:
開發者的困境:
這些問題過去只能靠各種 workaround 解決,體驗差強人意。
WICG 提出的這個提案,旨在打破 Canvas 和 DOM 之間的壁壘,讓 HTML 元素可以直接渲染到 Canvas 畫布上,同時保留 CSS 的全部能力與 DOM 的互動性。
layoutsubtree 屬性 — 開啟新世界的大門<canvas id="myCanvas" layoutsubtree width="800" height="600">
<div id="ui-panel">
<h2>遊戲選單</h2>
<button>開始遊戲</button>
</div>
</canvas>
layoutsubtree 的作用:
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);
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' });
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);
<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 幾乎不可能實現!

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

結合 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);

這個提案非常重視隱私安全,繪製時會自動過濾敏感資訊:
被過濾的敏感資訊:
允許的資訊:
啟用開發者實驗:
Demo 列表:
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();
可以在 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);
}
};
已實作:
進行中:
目前限制:
HTML-in-Canvas 是 Web 平台的一次重要演進:它讓開發者可以在 Canvas 的高效能繪圖能力與 HTML/CSS 的豐富表現力之間自由切換,而不再需要在可用性、排版品質與效能之間做痛苦的取捨。這個提案為遊戲 UI、圖表標籤、3D 場景貼圖等應用場景帶來更一致且強大的解決方案,同時維持對隱私與安全的保護。
專案連結:github.com/WICG/html-in-canvas
Demo 代碼基於 WICG 官方示例改編,侵刪。