需求
公司开发微信小程序,有一个海报页面,需要用户点击生成海报,可以将该该swipe-item 生成一个带二维码的图片,最终由纯前端实现!
技术调研
- 因为小程序的打包限制,不可能将所有的图片都放在代码目录里面。所以就得使用远端的图片,但是因为canvas对跨域图片的不支持,可以使用微信小程序中的API :wx.getImageInfo()现将远端的图片临时保存。
- canvas 组件是由客户端创建的原生组件,它的层级是最高的,不能通过 z-index 控制层级;并且使用display:none 和 visibility:hidden 的方式隐藏会导致canvas无法捕获画布内容,所以通过给canvas外部嵌套一个width:0;height:0;overflow:hidden的父级。
实现步骤
将远端图片暂存本地(以用户头像为例)
wx.getUserInfo({ //获取微信用户信息
success: function (res) {
this.getImageInfo(res.userInfo.avatarUrl); // 调取图片处理方法
this.setData({
userName: res.userInfo.nickName
});
}
});
getImageInfo(url){ // 图片缓存本地的方法
if(typeof url === 'string'){
wx.getImageInfo({ // 小程序获取图片信息API
src: url,
success: function (res) {
this.setData({
head_img:res.path
})
},
fail(err) {
console.log(err)
}
})
}
canvas绘制图片
点击生成海报按钮
handlePoster(e){
wx.getSetting({ // 获取用户设置
success(res) {
if (!res.authSetting['scope.writePhotosAlbum']) { // 如果用户之前拒绝了授权
wx.openSetting({
success(tag){
if (tag.authSetting["scope.writePhotosAlbum"]){ // 用户在设置页选择同意授权
// wx.showLoading({
// title: '正在生成...',
// })
}
}
});
}else{ // 用户已经授权
wx.showLoading({
title: '正在生成...',
})
}
}
})
此时页面出现loading状态,canvas开始绘制海报:
首先绘制整个底部背景
在这里,我暂且用本地图片作为演示代替。绘制的canvas图片大小为750px*1344px大小,最终生成格式为jpg。大小大概就200多kb。非常的小,清晰度也不错。
我选择的背景图如上图所示;
const ctx = wx.createCanvasContext('notes');
ctx.clearRect(0, 0, 0, 0);
const arr2 = ['./big_img1.png', this.data.nodesData[index].detail_pic]; // 有图片海报背景图&&海报正文图片
const WIDTH=750;
const HEIGHT=1334;
// 绘制图片模板的 底图
ctx.drawImage(arr2[0], 0, 0, WIDTH, HEIGHT);
绘制海报的正文图片
// 绘制图片模板的 banner图
ctx.drawImage(arr2[1], 40,40, 670, 580);
绘制时间
因为我们的海报上面的时间 xxxx.xx.xx中的每个数字和点都是图片,所以时间也是一个个的小的以数字命名的图片绘制上去。而后台提供给我们的时间是个 秒(s)为单位的时间戳。首先得将时间戳转换为一个时间数字组成的数组:
dateFilter(date){
let gmt = new Date(date * 1000);
let nYear=String(gmt.getFullYear());
let nMonth =this.numFormatting(gmt.getMonth()+1);
let nDay =this.numFormatting(gmt.getDate());
let end_date = (nYear + '.' + nMonth + '.' + nDay).split("") ;
return end_date
},
/**
* 处理 月份 天数 个位数
*/
numFormatting(num){
if(String(num).length<2){
return '0'+num
}else{
return num
}
},
假设处理好的时间格式为 :
let times=['2', '0', '1', '8', '.', '0', '4','.','1','2'];
接下来是绘制时间:
// 绘制 图片模板 的时间
const TEXT_DATE = ['2', '0', '1', '8', '.', '0', '4','.','1','2'];
for (let i = 0; i < TEXT_DATE.length; i++) {
if (TEXT_DATE[i] != '.') {
var path3 = `./${TEXT_DATE[i]}.png`;
} else {
var path3 = './point.png';
}
let clientx = 40 + 16 * i;
ctx.drawImage(path3, clientx, 640, 16, 32);
}
绘制用户头像
因为二维码中显示的用户头像是一个圆形图像,所以处理方式为:
// 绘制头像
ctx.save();
let r=32;
let d = r*2;
let cx = 102;
let cy = 1172;
ctx.arc(cx+r, cy+r, r, 0, 2 * Math.PI);
ctx.clip();
ctx.drawImage(this.data.head_img, cx, cy, d, d);
ctx.restore();
这个时候 ,海报所需要的所有图片已经绘制成功,接下来我们开始处理文字部分:
canvas 绘制文字
canvas并不会自动的将文字折行处理,所以我们得自己考虑每行显示多少个文字,还得考虑中英文和符号所占字节的不同,在此,封装一个处理方法:
textByteLength(text,num){ // text为传入的文本 num为单行显示的字节长度
let strLength = 0; // text byte length
let rows=1;
let str=0;
let arr=[];
for (let j = 0; j < text.length; j++) {
if (text.charCodeAt(j) > 255) {
strLength += 2;
if (strLength > rows * num) {
strLength++;
arr.push(text.slice(str, j));
str = j;
rows++;
}
} else {
strLength++;
if (strLength > rows * num) {
arr.push(text.slice(str, j));
str = j;
rows++;
}
}
}
arr.push(text.slice(str, text.length));
return [strLength, arr, rows] // [处理文字的总字节长度,每行显示内容的数组,行数]
},
这个时候 就可以开始绘制文字了:
绘制正文和 出处
const CONTENT_ROW_LENGTH = 40; // 正文 单行显示字符长度
let [contentLeng, contentArray, contentRows] = this.textByteLength(this.data.nodesData[index].text, CONTENT_ROW_LENGTH);
ctx.setTextAlign('left')
ctx.setFontSize(32);
let contentHh = 32 * 1.3;
for (let m = 0; m < contentArray.length; m++) {
ctx.fillText(contentArray[m], 40, 732 + contentHh * m);
}
// 绘制 出处
ctx.setTextAlign('right')
ctx.setFontSize(32);
ctx.fillText(`——${this.data.nodesData[index].refer}`, 710, 996, 710);
绘制二维码边上的说明
// 绘制二维码右边说明
ctx.setTextAlign('left')
ctx.setFontSize(28);
ctx.setFillStyle('rgba(34,34,34,.64)')
ctx.fillText('长按小程序码', 250, 1174);
ctx.fillText(`${this.data.userName}邀你进入掌阅读好书`, 250, 1230);
ctx.draw();
此时如果canvas标签能够显示查看,就能看到一个完美的海报绘制完成,现在只差最后一步,保存canvas;使用微信方法 canvasToTempFilePath()将canvas海报保存到本地临时文件路径;在使用saveImageToPhotosAlbum()将图片保存到本地相册:
setTimeout(function () {
wx.canvasToTempFilePath({
canvasId: 'notes',
fileType:'jpg',
success: function (res) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
wx.hideLoading();
wx.showToast({
title: '保存成功',
});
},
fail(){
wx.hideLoading()
}
})
}
})
}, 500);
然后查看存储图片的路径文件夹,最终生成的海报:
总结:
在做这个功能之前,canvas用的非常少,很多方法都是现成百度查询得出来的结果,这次的canvas生成海报并保存本地的实现过程中,让我学到了很多东西;因为项目做得比较急,很多的代码没有经过细致的打磨,在此,也希望看到本文章的朋友们及时指点。