使用html2canvas将页面转化为图片

最近的一个项目有个将页面转化为图片,并保存到用户本地的需求,找了一圈发现 html2canvas 好像可以实现需求,但是在实际使用时却遇到了一些问题。下面就是我遇到的问题和解决方法。

需求分析

解决问题之前先分析需求,这个需求基本可以分割为3个阶段

  • 在 canvas 绘制出 html
  • 将绘制好的 canvas 转为base64的图片
  • 下载图片

解决需求

第一步:使用 html2canvas 将 html 绘制到 canvas 画布上。
先到这里下载 html2canvas。查看文档,了解下使用方法,下面是一些需要设置的参数。
html2canvas API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 调用下面这个方法
// 传入2个参数 element:要转为图片的DOM元素,options:配置参数
html2canvas(element, options);
参数有下面这些
// allowTaint:true //允许加载跨域的图片
// tainttest:true //检测每张图片都已经加载完成
// scale:scaleBy // 添加的scale 参数
// canvas:canvas //自定义 canvas
// logging: false //日志开关,发布的时候记得改成false
// width:width //dom 原始宽度
// height:height //dom 原始高度
html2canvas(el, option).then(function(canvas){
document.body.appendChild(canvas);
// 第二步
});

第二步:现在已经有了 canvas 对象了,可以使用 canvas.toDataURL(type) 将画布上的内容,转为 base64 的图片。

1
2
3
4
5
6
7
8
9
var imgUri = canvas.toDataURL('image/png'); // 生成png格式的图片
// 将图片的 uri 设置到已经存在的img元素上。
document.querySelector('img').src = imgUri;
// 或者新建一个 img 元素,设置好src,然后再添加到DOM树中。
var newImg = document.createElement('img');
newImg.src = imgUri;
document.body.appendChild(newImg);

第三步:下载图片,可以使用 HTML5 提供的 download 属性

1
2
3
4
<a download="newImg.png" href="data:image/png;base64,xxxxxx">点我下载图片</a>
// download 是 HTML5 提供的一个新的属性。这个属性有一个值,作为下载文件的默认文件名。
// 将上一步图片的 base64 设置为 href 的值。
// 点击即可下载图片

遇到的问题

做完上面3个步骤后,需求基本就实现了,但现在却又出现一个新问题。
如果是移动端项目,那么生成的图片会很模糊。后来才知道,生成图片的尺寸是根据css像素来计算的,手机的物理像素要比css像素大很多,这样就会拉伸图片,造成失真。
后来找到了解决方案。原文链接

原文写的很清楚,在使用前需要修改下插件的源码,主要有2处。

修改源码

代码第 999 行 renderWindow 的方法中 修改判断条件 增加一个options.scale存在的条件:

1
2
3
4
5
6
7
8
9
// 源码
if (options.type === "view") {
canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
} else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
canvas = renderer.canvas;
} else {
canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});
}

改为

1
2
3
4
5
6
7
8
9
10
11
12
if (options.type === "view") {
canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
} else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement) {
canvas = renderer.canvas;
}else if(options.scale && options.canvas !=null){
log("放大canvas",options.canvas);
var scale = options.scale || 1;
canvas = crop(renderer.canvas, {width: bounds.width * scale, height:bounds.height * scale, top: bounds.top *scale, left: bounds.left *scale, x: 0, y: 0});
}
else {
canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});
}

代码第 943 行 html2canvas 的方法中 修改width,height:

1
2
3
4
5
6
7
8
// 源码
return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
if (typeof(options.onrendered) === "function") {
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
options.onrendered(canvas);
}
return canvas;
});

改为

1
2
3
4
5
6
7
8
9
width = options.width != null ? options.width : node.ownerDocument.defaultView.innerWidth;
height = options.height != null ? options.height : node.ownerDocument.defaultView.innerHeight;
return renderDocument(node.ownerDocument, options, width, height, index).then(function(canvas) {
if (typeof(options.onrendered) === "function") {
log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
options.onrendered(canvas);
}
return canvas;
});

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//定义查找元素方法
function $(selector) {
return document.querySelector(selector);
}
var main = {
init:function(){
main.setListener();
},
//设置监听事件
setListener:function(){
var btnShare = document.getElementById("btnShare");
btnShare.onclick = function(){
main.html2Canvas();
}
},
//获取像素密度
getPixelRatio:function(context){
var backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
},
//绘制dom 元素,生成截图canvas
html2Canvas: function () {
var shareContent = $("#shareContent");// 需要绘制的部分的 (原生)dom 对象 ,注意容器的宽度不要使用百分比,使用固定宽度,避免缩放问题
var width = shareContent.offsetWidth; // 获取(原生)dom 宽度
var height = shareContent.offsetHeight; // 获取(原生)dom 高
var offsetTop = shareContent.offsetTop; //元素距离顶部的偏移量
var canvas = document.createElement('canvas'); //创建canvas 对象
var context = canvas.getContext('2d');
var scaleBy = main.getPixelRatio(context); //获取像素密度的方法 (也可以采用自定义缩放比例)
canvas.width = width * scaleBy; //这里 由于绘制的dom 为固定宽度,居中,所以没有偏移
canvas.height = (height + offsetTop) * scaleBy; // 注意高度问题,由于顶部有个距离所以要加上顶部的距离,解决图像高度偏移问题
context.scale(scaleBy, scaleBy);
var opts = {
allowTaint:true,//允许加载跨域的图片
tainttest:true, //检测每张图片都已经加载完成
scale:scaleBy, // 添加的scale 参数
canvas:canvas, //自定义 canvas
logging: true, //日志开关,发布的时候记得改成false
width:width, //dom 原始宽度
height:height //dom 原始高度
};
html2canvas(shareContent, opts).then(function (canvas) {
console.log("html2canvas");
var body = document.getElementsByTagName("body");
body[0].appendChild(canvas);
});
}
};
//最后运行代码
main.init();

注意事项

  • 前面的内容没看过,没下载过html2canvas.js 没按照插件改过说明操作的先改好再说
  • 注意元素的样式的使用: 外层元素width 不能使用百分比 ,避免导致图片与文字间缩放比例问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 错误使用方式如
    .container {
    width:50%;
    margin: 0 auto;
    }
    // 需要改为
    .container {
    width:300px;
    margin: 0 auto;
    }