HTML-in-Canvas:讓 Canvas 完美渲染 HTML 的 Web 新標準

專案來源:WICG(Web Platform Incubator Community Group)
GitHubgithub.com/WICG/html-in-canvas


一、為什麼需要這個提案?

當前的痛點

長期以來,<canvas></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">
  <div id="ui-panel">
    <h2>遊戲選單</h2>
    <button>開始遊戲</button>
  </div>
</canvas>

layoutsubtree 的作用:

  • 允許 Canvas 的直接子元素參與排版(layout)
  • 建立新的堆疊上下文(stacking context)
  • 成為所有後代元素的包含區塊(containing block)
  • 啟用命中測試(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 幾乎不可能實現!

截屏2026-04-11 23.55.13.png

場景 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 列表:


六、技術細節

座標系轉換

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 的高效能繪圖能力與 HTML/CSS 的豐富表現力之間自由切換,而不再需要在可用性、排版品質與效能之間做痛苦的取捨。這個提案為遊戲 UI、圖表標籤、3D 場景貼圖等應用場景帶來更一致且強大的解決方案,同時維持對隱私與安全的保護。

專案連結github.com/WICG/html-in-canvas


Demo 代碼基於 WICG 官方示例改編,侵刪。


原文出處:https://juejin.cn/post/7627135662009073718


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝5   💬6   ❤️3
429
🥈
我愛JS
📝2   💬7   ❤️2
207
🥉
💬1  
4
#4
Gigi
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登