将DOM对象绘制到canvas中

作者 Simmin 日期 2016-12-03
将DOM对象绘制到canvas中

之前要做一个将DOM生成图片的示例,因为DOM的内容是动态生成的,所以不能用直接截图的方式,然后发现了一个很好用的插件:html2canvas。

一、html2canvas简介

官方链接:http://html2canvas.hertzen.com/
github地址:https://github.com/niklasvh/html2canvas

使用方法很简单:

1.引入JQuery和html2canvas.js

<script type="text/javascript" src="./js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="./js/html2canvas.js"></script>

html2canvas是基于JQuery的,所以要先引入jQuery文件

2.调用插件

html2canvas(document.body, {
allowTaint: true,
taintTest: false,
onrendered: function(canvas) {
canvas.id = "mycanvas";
//document.body.appendChild(canvas);
//生成base64图片数据
var dataUrl = canvas.toDataURL();
var newImg = document.createElement("img");
newImg.src = dataUrl;
document.body.appendChild(newImg);
}
});

通过html2canvas方法调用插件

html2canvas(element, options);

该方法接收两个参数,element为要生成截图的DOM元素,options为插件的配置。

名称 类型 默认值 描述
allowTaint boolean false 是否允许跨域图片污染canvas
background string #fff 如果在DOM中没有指定,设置它为canvas背景色
height number null 定义canvas的高度(px)。如果没有,渲染成window的高度
letterRendering boolean false 是否分开渲染每个字母。如果用了letter-spacing,那它是必须的
logging boolean false 是否在console里记录事件
proxy string undefined 这个代理url是被用于加载跨域图片。如果为空,跨域图片将不会被加载。
taintTest boolean true 在画图之前是否测试每个图片有没有污染了canvas
timeout number 0 加载图片的超时事件(ms)。设置为0,将导致没有超时。
width number null 定义canvas的宽度(px)。如果没有,渲染成window的宽度
useCORS boolean false 在恢复代理之前,是否尝试加载跨域图片为CORS服务

(翻译的可能不太准确)

渲染出的canvas通过onrendered事件回调,如上面的例子

html2canvas的源码没有详细去读,具体是采用何种方式实现暂不清楚。

但是不能直接把html画到canvas上,一种可行的方案是先使用<foreignObject>元素包含HTML内容,从而生成SVG图像,再将这个图像绘制到canvas中。

二、DOM->Canvas

创建一个包含XML字符串的SVG,然后构造一个Blob对象。

Blob对象是包含有只读原始数据的类文件对象。

Blob对象的要求:

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

foreignObject元素允许包含外来的XML命名空间,其图形内容是别的用户代事绘制的。这个被包含的外来图形内容服从SVG变形和合成。

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;

仔细看代码会发现,其实是先为生成的svg图像创建了一个url路径,然后以此创建img对象,在这个img对象load时将图像画到canvas上。

SVG必须使用有效的HTML,这里通过XML解析器来解析HTML代码

var doc = document.implementation.createHTMLDocument("");
doc.write(html);
doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI); //设置命名空间
html = (new XMLSerializer).serializeToString(doc);

implementation属性可以返回与当前文档相关的DOMImplementation接口,该接口是一个特殊的用于提供服务的接口,可以控制操作一篇文档。

DOMImplementation接口提供很多方法,可以用来操作独立于任何特定的文档对象模型实例。

DOMImplementation接口中常用的方法有以下四个:

①creatDocument()方法可以创建一个指定类型的XML文档对象。

②createDocumentType()创建空的 DocumentType 节点。

③createHTMLDocument()方法用来创建一个新的HTML文档。

④hasFeature()方法是否可执行指定的特性和版本。

这样的简单dom到图片或dom到canvas基本实现了。但是这个暂时有几个问题:

1.html 若不使用内联样式,而是用内部样式或引入外部样式,svg如何渲染?

内部样式可以通过 document.styleSheets中cssRules 或rules 获取。

外部样式的链接可以通过document.styleSheets中的href获取,但暂时不知道外部样式要如何添加到foreignObject中才能生效。

2.img元素无法在foreignObject插入,无法加载对应的图片。

三、更多的思考

Blob 是什么? 它是一种JavaScript的对象类型。

file 对象其实就是 blob 对象的一个更具体的版本,blob 存储着大量的二进制数据,并且 blob 的 size 和 type 属性,都会被 file 对象所继承。

所以, 在大多数情况下,blob 对象和 file 对象可以用在同一个地方,例如,可以使用 FileReader 接口从 blob 读取数据,也可以使用 URL.createObjectURL() 从 blob 创建一个新的 URL 对象。

可以直接通过 Blob() 的构造函数来创建一个Blob对象。

构造函数,接受两个参数:

  1. 第一个为一个数据序列,可以是任意格式的值。

  2. 第二个参数,是一个包含了两个属性的对象,其两个属性分别是:

    a. type – MIME 的类型。

    b. endings – 决定 append() 的数据格式,(数据中的 \n 如何被转换)可以取值为 “transparent” 或者 “native”(t 的话不变,n 的话按操作系统转换;t* 为默认) 。

关于window.URL.createObjectURL

URL对象是硬盘(SD卡等)指向文件的一个路径,如果我们做文件上传的时候,想在没有上传服务器端的情况下看到上传图片的效果图的时候就可是以通过var url=window.URL.createObjectURL(obj.files[0]);获得一个http格式的url路径,这个时候就可以设置到<img>中显示了。

安全问题

这种方法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。

这也就是前面问题中提到的无法加载外部css和图片的原因。

而html2canvas在这方面做的比较完善,所以愉快地使用html2canvas吧,如果能研究一下它的源码就更好啦!

参考资料:

  1. 将 DOM 对象绘制到 canvas 中
  2. Blob
  3. foreignObject