将 DOM 对象绘制到 canvas 中

前言

我们推荐您先熟悉 JavaScriptCanvas APIDOM API,这样有助于理解这篇文章。

如果您还熟悉 SVG 那就更好了。

DOM 内容(如 HTML)绘制到 canvas 中是可行的,尽管这不常见(出于安全原因)。这篇文章源自 Robert O'Callahan 的博客,讲述如何按照规范安全地实现这个功能。

概述

您不能直接把 HTML 画到 canvas 上。您需要使用一个包含您想要呈现内容的 SVG 图像。为了绘制 HTML 内容,您要先使用 <foreignObject> 元素包含 HTML 内容,再将这个 SVG 图像绘制到您的 canvas 中。

步骤

夸张地说,这里唯一真正棘手的事情就是创建 SVG 图像。您需要做的所有事情是创建一个包含 XML 字符串的 SVG,然后根据下面的几部分构造一个 Blob

  1. blob 对象的 MIME 应为 "image/svg+xml"。
  2. 一个 <svg> 元素。
  3. 在 SVG 元素中包含的 <foreignObject> 元素。
  4. 包裹到 <foreignObject> 中的(格式化好的) HTML。

如上所述,通过使用一个 object URL,我们可以内联 HTML 而不是从外部源加载它。当然,只要域与原始文档相同(不跨域),您也可以使用外部源。

示例

HTML

<canvas id="canvas" style="border:2px solid black;" width="200" height="200">

JavaScript

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
           '<foreignObject width="100%" height="100%">' +
           '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
             '<em>I</em> like' +
             '<span style="color:white; text-shadow:0 0 2px blue;">' +
             'cheese</span>' +
           '</div>' +
           '</foreignObject>' +
           '</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);
img.onload = function () {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);
}
img.src = url;

效果:

ScreenshotLive sample

data 变量设置了我们想要绘制到 canvas 上的 SVG 图像的内容(里面包括了想要绘制的 HTML)。

接下来通过调用 new Image() 我们创建了一个新的 HTML <img> 元素,然后嵌入 data、分配一个 object URL,再在图片载入时通过 drawImage() 把它绘制到画布中。

安全性

您可能想知道这是否安全:担心 canvas 会读取到敏感数据。答案是这样的:这个解决方案所依赖的 SVG 图像在实现上是非常严格的。SVG 图像不允许加载任何外部资源,即使看上去来自同一个域。资源如栅格化图像(如 JPEG 图像)或 <iframe> 需要用 data: URIs 来内联引入。

此外,您也不能在 SVG 图像中各种引入脚本文件,因此不会有从其他脚本文件访问 DOM 的风险。SVG 图像中的 DOM 元素也不能接收事件的输入,因此无法将敏感信息载入到一个表单控件(如将完整路径载入到 file <input> 元素中)渲染再通过读取图像获取这些信息。

已访问的链接样式(:visited)不会对 SVG 图像中的链接生效,因此无法获取浏览历史;SVG 图像中也不会渲染原生主题,因此借此检测用户的平台也会更困难。

生成的 canvas 元素是纯净的,您可以通过调用 toBlob(function(blob){…}) 来返回 canvas 的数据块,或者 toDataURL() 来返回 Base64 编码的 data: URI。

绘制 HTML

由于 SVG 必须是是有效的 XML,因此您需要解析 HTML 来使它符合格式。下面的代码可以很方便地解析 HTML。

var doc = document.implementation.createHTMLDocument("");
doc.write(html);
// You must manually set the xmlns if you intend to immediately serialize
// the HTML document to a string as opposed to appending it to a
// <foreignObject> in the DOM
doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI);
// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc);

另请参阅

文档标签和贡献者