将 DOM 内容(如 HTML)绘制到 canvas 中是可行的,尽管这不常见(出于安全原因)。这篇文章源自 Robert O'Callahan 的博客,讲述如何按照规范安全地实现这个功能。
概述
您不能直接把 HTML 画到 canvas 上。您需要使用一个包含您想要呈现内容的 SVG 图像。为了绘制 HTML 内容,您要先使用 <foreignObject>
元素包含 HTML 内容,再将这个 SVG 图像绘制到您的 canvas 中。
步骤
夸张地说,这里唯一真正棘手的事情就是创建 SVG 图像。您需要做的所有事情是创建一个包含 XML 字符串的 SVG,然后根据下面的几部分构造一个 Blob
。
- blob 对象的 MIME 应为 "image/svg+xml"。
- 一个
<svg>
元素。 - 在 SVG 元素中包含的
<foreignObject>
元素。 - 包裹到
<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;
效果:
Screenshot | Live 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);
另请参阅
- Canvas
- Canvas 教程
- Drawing DOM content to canvas (blog post)
- rasterizeHTML.js,根据这篇博客的一个实现