0%

Javascript保存网页元素为图片并下载到本地

最近开发了一个应用包管理平台,主要提供给内部人员使用,可以扫码下载App安装包,因为还要平台外的分发,所以需要提供个二维码保存的功能。

分析

针对网页元素保存为图片,我可以用的是html2canvas,二维码生成我原来用的qrcode-react 库,但是发现这个图无法绘制到Canvas中,且这个库没有提供生成图片的回调,因此要实现我的功能只能二次开发下这个库了。

开始

生成二维码并回传图片信息

建一个二维码处理组件,可以看到依赖qr.js:

1
npm install qr.js

安装好后,创建文件 qrcode-reactjs.js

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
'use strict'

var React = require('react');
var PropTypes = require('prop-types');
var ReactDOM = require('react-dom');
var qr = require('qr.js');

function getBackingStorePixelRatio(ctx) {
return (
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1
);
}

var getDOMNode;
if (/^0\.14/.test(React.version)) {
getDOMNode = function(ref) {
return ref;
}
} else {
getDOMNode = function(ref) {
return ReactDOM.findDOMNode(ref);
}
}

class QRCode extends React.Component {
shouldComponentUpdate(nextProps) {
var that = this;
return Object.keys(QRCode.propTypes).some(function(k) {
return that.props[k] !== nextProps[k];
});
}

componentDidMount() {
this.update();
}

componentDidUpdate() {
this.update();
}

utf16to8(str) {
var out, i, len, c;
out = "";
len = str.length;
for (i = 0; i < len; i++) {
c = str.charCodeAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
out += str.charAt(i);
} else if (c > 0x07FF) {
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
} else {
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}
}
return out;
}

update() {
var value = this.utf16to8(this.props.value);
var qrcode = qr(value);
var canvas = getDOMNode(this.refs.canvas);

var ctx = canvas.getContext('2d');
var cells = qrcode.modules;
var tileW = this.props.size / cells.length;
var tileH = this.props.size / cells.length;
var scale = (window.devicePixelRatio || 1) / getBackingStorePixelRatio(ctx);
canvas.height = canvas.width = this.props.size * scale;
ctx.scale(scale, scale);

cells.forEach(function(row, rdx) {
row.forEach(function(cell, cdx) {
ctx.fillStyle = cell ? this.props.fgColor : this.props.bgColor;
var w = (Math.ceil((cdx + 1) * tileW) - Math.floor(cdx * tileW));
var h = (Math.ceil((rdx + 1) * tileH) - Math.floor(rdx * tileH));
ctx.fillRect(Math.round(cdx * tileW), Math.round(rdx * tileH), w, h);
}, this);
}, this);

if (this.props.logo) {
var self = this
var size = this.props.size;
var image = document.createElement('img');
image.src = this.props.logo;
image.onload = function() {
var dwidth = self.props.logoWidth || size * 0.2;
var dheight = self.props.logoHeight || image.height / image.width * dwidth;
var dx = (size - dwidth) / 2;
var dy = (size - dheight) / 2;
image.width = dwidth;
image.height = dheight;
console.log('dwidth', dwidth)
console.log('dheight', dheight)
ctx.drawImage(image, dx, dy, dwidth, dheight);
}
}
if (this.props.onCanvasLoad){
this.props.onCanvasLoad(canvas.toDataURL('image/png'))
}
}

render() {
return React.createElement('canvas', {
style: { height: this.props.size, width: this.props.size },
height: this.props.size,
width: this.props.size,
ref: 'canvas',
});
}
}

QRCode.propTypes = {
value: PropTypes.string.isRequired,
size: PropTypes.number,
bgColor: PropTypes.string,
fgColor: PropTypes.string,
logo: PropTypes.string,
logoWidth: PropTypes.number,
logoHeight: PropTypes.number,
onCanvasLoad: PropTypes.func,
};

QRCode.defaultProps = {
size: 128,
bgColor: '#FFFFFF',
fgColor: '#000000',
value: 'http://facebook.github.io/react/'
};

module.exports = QRCode;

调用方式

1
2
3
4
import QRCode from '@/utils/qrcode-reactjs'

<QRCode value={downloadURL} size={250} onCanvasLoad={dataUrl => this.handleImageLoad(dataUrl)} />

根据Base64内容创建图片Dom

这里做了个1秒延迟,防止二维码初始化时的报错,图片加载后注意把原来的QRCode节点隐藏。

1
2
3
4
5
6
7
8
9
10
11
12
handleImageLoad = dataUrl => {
const { imgUrl } = this.state;
if (imgUrl !== dataUrl) {
setTimeout(() => {
this.setState({
imgUrl: dataUrl,
})
}, 1000)
}
}

{imgUrl && ( <div><img src={imgUrl} style={{ height: 250, width: 250 }} alt=""/> </div>)}

保存图片到本地

保存元素到图片我用的html2canvas,支持保存整个页面,也支持保存指定的元素Dom。我们实际只需要二维码和下面的描述信息。所以直接给需要的元素加上ref即可,也可以给个id,自己查找下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
handleSaveImg = packageName => {
html2canvas(this.qrCodeImg, { allowTaint: true, useCORS: true }).then(canvas => {
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = `${packageName}_${new Date().getTime().toString()}`;
a.click();
});
}

<div ref={node => { this.qrCodeImg = node }} className={styles.flavorList} >
...
</div>

<Button type="primary" onClick={() => this.handleSaveImg(packageInfo.name)}> 下载二维码 </Button>

结语

至此功能已经完成了,需要的试试吧。