solution-html生成图片

一、背景

在 内容合作协议H5 的业务中,提供一个H5页面,供运营&法务同学线上签约付费用户。其中有一个场景,需要生成“合同页”的长图片
解决思路: html -> canvas -> image -> a[download]

单击查看demo

demo源码地址:https://github.com/hfuuss/jiuyueTest/blob/master/html2canvas/index.html

二、解决方案

  • 方案1:将DOM改写为canvas,然后利用canvas的toDataURL方法实现将DOM输出为包含图片展示的data URI
  • 方案2:使用html2canvas.js实现(可选搭配Canvas2Image.js实现网页保存为图片)
  • 方案3:使用rasterizeHTML.js实现

方案对比选择

  • 方案1:需要手动计算每个DOM元素的Computed Style,然后需要计算好元素在canvas的大小位置等属性。
    方案1难点:
    相当于完全重写了整个页面的布局样式,增加了工作量。
    由于canvas中没有的对象概念,对于元素丰富、布局复杂的页面,不易重构。
    所有DOM元素改写进canvas会带来一些困难,例如:难以支持响应式,图片元素清晰度不佳和文字点击区域识别问题等。
  • 方案2:该类功能中Github上stars最多(至今仍在维护),Stack Overflow亦有丰富的讨论。只需简单调用html2canvas方法并设定配置项即可。(配合 canvas2image可以很方便转换为图片)
  • 方案3:该方案的限制较多,目前仅支持3类可转为canvas的目标格式: 页面url,html字符串和document对象。

小结: html2canvas是目前实现网页保存为图片功能的综合最佳选择。

相关文档:

html2canvas:https://github.com/niklasvh/html2canvas
canvas2image:https://github.com/hongru/canvas2image

三、生成图片的清晰度优化方案

因为canvas不是矢量图,而是像图片一样是位图模式的。如果不做Retina屏适配的话,例如二倍屏,浏览器就会以2个像素点的宽度来渲染一个像素,该canvas在Retina屏幕下相当于占据了2倍的空间,相当于图片被放大了一倍,因此图片会变模糊。

3.1 清晰度优化方案

要做Retina屏适配,关键是知道当前屏幕的设备像素比,然后将canvas放大到该设备像素比来绘制,然后将canvas压缩到一倍来展示。

  • 涉及到的知识点:
    要设置canvas的画布大小,使用的是canvas.width 和 canvas.height;
    要设置画布的实际渲染大小,使用的style或CSS设置的 width 和height,只是简单的对画布进行缩放。
    注意事项:canvas中的线条大小、文字大小等都需要乘以设备像素比来进行绘制,否则高倍屏下的线条会变细几倍
    解决方案代码示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    /**
    * 根据window.devicePixelRatio获取像素比
    */
    function DPR() {
    if (window.devicePixelRatio && window.devicePixelRatio > 1) {
    return window.devicePixelRatio;
    }
    return 1;
    }
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
/**
* 将传入值转为整数
*/
function parseValue(value) {
return parseInt(value, 10);
};

convert2canvas(cntElem) {
var shareContent = cntElem;//需要截图的包裹的(原生的)DOM 对象
var width = parseValue(shareContent.offsetWidth); //获取dom 宽度
var height = parseValue(shareContent.offsetHeight); //获取dom 高度
var canvas = document.createElement("canvas"); //创建一个canvas节点
var scale = DPR(); //定义任意放大倍数 支持小数
canvas.width = width * scale; //定义canvas 宽度 * 缩放
canvas.height = height * scale; //定义canvas高度 *缩放
canvas.getContext("2d").scale(scale, scale); //获取context,设置scale
var opts = {
scale: scale, // 添加的scale 参数
canvas: canvas, //自定义 canvas
// logging: true, //日志开关,便于查看html2canvas的内部执行流程
width: width, //dom 原始宽度
height: height,
useCORS: true // 【重要】开启跨域配置
};
html2canvas(shareContent, opts).then(function (canvas) {
var context = canvas.getContext('2d');
// 【重要】关闭抗锯齿
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;

// 【重要】默认转化的格式为png,也可设置为其他格式
var img = Canvas2Image.convertToJPEG(canvas, canvas.width, canvas.height);
document.body.appendChild(img);
$(img).css({
"width": canvas.width / 2 + "px",
"height": canvas.height / 2 + "px",
});
});
}

3.5: 下载方案的坑

使用canvas2image的中的saveAsImage()的时候,在Chrome中的手机模式下调试的时候,网页会跳转到一个新的网页,但是不会下载图片。经过调研,采用新的下载方案:

旧方案:

1
2
3
function saveFile (strData) {
document.location.href = strData;
}

新方案:

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
function saveFile (strData) {
jiuyueDownload(strData);
}


//下载图片
function jiuyueDownload(imgData) {
this.downloadFile('test', imgData);
}
//下载
function downloadFile(fileName, content) {
let aLink = document.createElement('a');
let blob = this.base64ToBlob(content); //new Blob([content]);

let evt = document.createEvent("HTMLEvents");
evt.initEvent("click", true, true);//initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
aLink.download = fileName;
aLink.href = URL.createObjectURL(blob);

// aLink.dispatchEvent(evt);
aLink.click()
}
//base64转blob
function base64ToBlob(code) {
let parts = code.split(';base64,');
let contentType = parts[0].split(':')[1];
let raw = window.atob(parts[1]);
let rawLength = raw.length;

let uInt8Array = new Uint8Array(rawLength);

for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {type: contentType});
}

四、部分技术使用样例

html2canvas使用:

1
2
3
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});

2. canvas2image 使用

1
2
3
4
5
6
7
8
9
10
11
Canvas2Image.saveAsImage(canvasObj, width, height, type)
Canvas2Image.saveAsPNG(canvasObj, width, height)
Canvas2Image.saveAsJPEG(canvasObj, width, height)
Canvas2Image.saveAsGIF(canvasObj, width, height)
Canvas2Image.saveAsBMP(canvasObj, width, height)

Canvas2Image.convertToImage(canvasObj, width, height, type)
Canvas2Image.convertToPNG(canvasObj, width, height)
Canvas2Image.convertToJPEG(canvasObj, width, height)
Canvas2Image.convertToGIF(canvasObj, width, height)
Canvas2Image.convertToBMP(canvasObj, width, height)

3. html2canvas、canvas2image、jQuery结合使用

1
2
3
4
5
6
7
8
9
$(document).ready( function(){
$(".example1").on("click", function(event) {
event.preventDefault();
html2canvas($(".test")[0]).then(function(canvas) {
document.body.appendChild(Canvas2Image.convertToImage(canvas,canvas.width,canvas.height))//转换图片
Canvas2Image.saveAsPNG(canvas, canvas.width, canvas.height);//保存图片
});
});
});

五、其它

由于html2canvas是将dom“画”的canvas上面,可能会受到许多布局属性的影响: z-index、float、position等
如果页面中存在跨域的图片,可能会导致无法“绘制”图片,解决方案需要俩点:

  • 1、在html2canvas中配置useCORS: true
  • 2、利用CORS解决跨域问题。