业务场景:

H5 的客服中,根据用户输入付款金额以及选择付款方式,生成一张带有付款二维码、付款金额、若干提示语的图片。

封装绘制方法的基本思路:

  1. 调用接口获得二维码图片地址
  2. new Image()并 onload 后在 canvas 上绘制图片生成信息
  3. 完成绘制后执行回调,返回 canvas
  4. 回调中调用 canvas.toDataURL(‘image/png’)将 canvas 转成 base64

遇到的问题:

在 IOS 中无法绘制图片,经过排查发现是因为 onload 事件失效,所以使用 fetch()获取图片,在 then()中做后续操作。

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
// 调用绘制二维码
this.drawPayQRCode({
qrImgUrl: '二维码地址',
logoImgUrl: '支付通道的 logo',
sum: '金额',
userLoginName: '用户名',
branchName: '彩店名',
payproductName: '支付通道名',
tipList: ['文案 1','文案 2','文案 3'],
callback: function(canvas) {
const imgUrl = canvas.toDataURL('image/png');
}
});
// 定义绘制二维码的方法
drawPayQRCode(options) {
const canvas = document.createElement('canvas');
const canvasWidth = 280;
const canvasHeight = 270;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const context = canvas.getContext('2d');

// 定义背景色
context.fillStyle = '#fff';
context.fillRect(0, 0, canvasWidth, canvasHeight);

//定义二维码和支付通道 logo 的位置
const qrL = (280 - 150) / 2;
const qrR = 26;
const qrWH = 150;
const logoL = (280 - 24) / 2;
const logoR = 89;
const logoWH = 24;

const qrImg = new Image();
// 判断机型为 ios
if (CONFIG.browser.ios) {
// 加载二维码图片
this.fetchImage(options.qrImgUrl, function(myBlob) {
const objectURL = URL.createObjectURL(myBlob); // 将 Blob 生成 URL
qrImg.src = objectURL;

// 延迟检查图片是否加载完成,可以改为轮询形式,complete 成功后关掉定时器
setTimeout(function() {
if (qrImg.complete) {
// 绘制二维码图片
context.drawImage(qrImg, qrL, qrR, qrWH, qrWH);
}

const logoImg = new Image();
// 加载支付通道 logo,原理同上
this.fetchImage(options.logoImgUrl, function(myBlob) {
const objectURL = URL.createObjectURL(myBlob);
logoImg.src = objectURL;
setTimeout(function() {
if (logoImg.complete) {
context.drawImage(logoImg, logoL, logoR, logoWH, logoWH);
// 完成绘制后,执行回调
options.callback(canvas);
}
}, 60);
});
}, 60);
});
} else {
//图片跨域访问
qrImg.setAttribute('crossOrigin', 'Anonymous');

qrImg.onload = function() {
context.drawImage(qrImg, qrL, qrR, qrWH, qrWH);
//下载支付通道 logo
const logoImg = new Image();
logoImg.setAttribute('crossOrigin', 'Anonymous');

logoImg.onload = function() {
context.drawImage(logoImg, logoL, logoR, logoWH, logoWH);
// 完成绘制后,回调
options.callback(canvas);
};
logoImg.src = options.logoImgUrl;
};
qrImg.src = options.qrImgUrl;
}

context.font = '12px Helvetica Neue,Tahoma,Arial,PingFangSC-Regular,Hiragino Sans GB,Microsoft Yahei,sans-serif';

// 金额样式
context.fillStyle = '#f00';
context.textAlign = 'center';
context.fillText(`¥${options.sum}`, 140, 26 + 150 + 10);
context.fillText(`仅限${options.userLoginName} ${options.branchName}使用`, 140, 26 + 150 + 25);

// 提示语样式
context.fillStyle = '#000';
context.textAlign = 'left';
const tipList = options.tipList;

//如果是微信付款就添加提交数据
if (options.payproductName === 'WECHAT') {
tipList[0] = '请打开微信 APP 扫一扫';
tipList.splice(2, 0, '请勿在扫码页面从相册选取二维码进行识别');
}

const len = tipList.length;
for (let i = 0; i < len; i++) {
context.fillText(i + 1 + '.' + tipList[i] + (i === len - 1 ? '。' : ';'), 16, 220 + 16 \* i);
}
},
fetchImage(imgUrl, cb) {
fetch(imgUrl)
  .then(function(res) {
if (res.ok) {
return res.blob();
}
  })
  .then(function(myBlob) {
cb(myBlob);
  });
},

       由于fetch有些兼容性的问题,所以会考虑到用xhr来实验一下,用xhr来获取图片的Blob:

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
<img id="myImg" src="" />
<script>
function loadImage(url, callback) {
if (typeof callback !== 'function') {
callback = function(url) {
console.log(url);
};
}
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(req) {
console.log(xhr.response)
const resToUrl = window.URL.createObjectURL(xhr.response);
console.log(resToUrl)
callback(resToUrl);
};
xhr.open('GET', url, true);
xhr.send();
}

//使用方法
var imgUrl = 'http://qimg.hxnews.com/2019/0203/1549199908936.jpg';
loadImage(imgUrl, function(url) {
document.getElementById('myImg').setAttribute('src', url);
});
</script>