这个需求
做了一个小程序,要将一个商品的信息生成一张海报,并且要把这张海报保存到相册。
刚接到这个任务的时候,还是蛮兴奋的,毕竟以前没有做过,又可以搞搞“新”技术了。
技术要点一:
保存相册功能倒是简单,因为小程序开发文档里已经有了非常详细的介绍,只是调用一个官方API:wx.saveImageToPhotosAlbum (如果使用的是uniapp,那把wx改成uni就好了)
。
但是,需要注意的是你保存的图片,如果的网络图片(即通过接口请求的图片),那你必须通过wx.getImageInfo
方法获取到它的信息,成功回调函数里会返回一个path
,将这个path
,传给wx.saveImageToPhotosAlbum
才行。
wx.getImageInfo({
src: picUrl,
success: (res) => {
if (res.path) {
wx.saveImageToPhotosAlbum({
filePath: res.path,
success: function () {
wx.showToast({icon: 'none', title: '保存成功'});
},
fail: function (err) {
wx.showToast({icon: 'none', title: err});
},
});
} else {
wx.showToast({icon: 'none', title: '下载失败'});
}
}
})
嗯,一切准备就绪,感觉可以上线了......但是,你可能还是太年轻了。
不信,你用手机测试一下——保存成功是成功了,可是,当你打开手机相册查看的时候,你会发现,你相册里最后居然是一张空白的照片,惊不惊喜?意不意外?
我当时反正是挺意外的,为什么呢?
因为,没有认真看文档,里面有一句非常关键的话——网络图片需先配置 download
域名才能生效。
是的,必须到小程序管理后台把这类图片的域名添加到合法域名列表......
嗯,就是这里。
这~就大功告成了!
不过,别高兴得太早,到这里可能只是完成了1/10 而已,还有很多工作要做,因为你的生成的是一张关于产品的海报,海报的信息可不只是一张产品图片。
如何把很多动态信息,组合在一起生成一张图片呢?
真正的技术难点:
一开始,我想到的是html2canvas插件,但是几经尝试后,发现小程序没有dom,貌似无法成功。查了很多资料,也根据他们说的方法一字一句地敲好了代码,可就是不成功......于是,只好放弃。
既然插件没办法用,只好自己写咯,所以很自然地想到了canvas
,这里技术本身并不验证,基本上是一些细节问题,所以直接上代码:
createCanvas(){
const _self = this;
const ctx = wx.createCanvasContext('shareCanvas', _self);
// 绘制背景白色
ctx.fillStyle="#FFFFFF";
ctx.fillRect(0,0, 640, 840);
ctx.setFillStyle('#333333');
ctx.setFontSize(36);
ctx.fillText('文字', 270, 120);
// 文字换行显示
ctx.setFillStyle('#B5B6B7')
ctx.setFontSize(22);
const otherName = '假设这是一串很长的文字';
_self.drawText(ctx, otherName, 150, 770, 22, 290);
// 二维码绘制
ctx.drawImage(_self.qrcodeUrlLocal, 90, 210, 460, 460);
// 头像绘制成圆形
_self.circleImgTwo(ctx, _self.avatarUrlLocal, 25, 690, 108, 108, 100);
// 绘制整图
ctx.draw(false, (()=>{
setTimeout(() => {
// 把canvas生成为图片
_self.tempFileImage()
},300);
})())
}
// 将头像绘制成圆形
circleImgTwo(ctx, img, x, y, w, h, r) {
// 画一个图形
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.strokeStyle = '#FFFFFF'; // 设置绘制圆形边框的颜色
ctx.stroke();
ctx.clip();
ctx.drawImage(img, x, y, w, h);
},
// 文本换行显示
drawText: function(ctx, str, leftWidth, initHeight, titleHeight, canvasWidth) {
let lineWidth = 0;
let lastSubStrIndex = 0; //每次开始截取的字符串的索引
for (let i = 0; i < str.length; i++) {
lineWidth += ctx.measureText(str[i]).width;
if (lineWidth > canvasWidth) {
ctx.fillText(str.substring(lastSubStrIndex, i), leftWidth, initHeight); //绘制截取部分
initHeight += 28; //22为 文字大小20 + 2
lineWidth = 0;
lastSubStrIndex = i;
titleHeight += 28;
}
if (i == str.length - 1) { //绘制剩余部分
ctx.fillText(str.substring(lastSubStrIndex, i + 1), leftWidth, initHeight);
}
}
// 标题border-bottom 线距顶部距离
titleHeight = titleHeight + 10;
return titleHeight;
},
tempFileImage() {
const _self = this;
wx.canvasToTempFilePath({
canvasId: 'shareCanvas',
success: (res) => {
// 保存当前绘制图片
_self.savePic(res.tempFilePath)
},
fail: function(err) {
wx.showToast({
title: '图片生成失败'
});
}
}, _self)
},
savePic(filePath) {
const _self = this;
wx.saveImageToPhotosAlbum({
filePath: filePath,
success: function() {
wx.showToast({
title: '图片保存成功'
});
},
fail: function(e) {
wx.showToast({
title: '图片保存失败'
});
},
complete: function(e) {
}
});
}
这里只需要注意一下 drawText()
、circleImgTwo()
就可以了,这是很常用业务逻辑,开发的时候很可能会需要,所以记录一下。特别是ctx.measureText
可以计算字体的大小,也就是每个字占用的宽高,用于文字换行的绘制。
对了,别忘了把html代码写上:
<canvas canvas-id="shareCanvas" class="canvas"></canvas>
注意要用canvas-id
属性来定义元素。
本来,写到这里呢,基本上已经完成了。
将base64图片绘制到canvas
问题是,生成海报的目的是为了引流,引流就不能没有“码”(小程序码或者二维码),有码其实也没什么,毕竟它也只是一张图片而已,但问题是,这张图片来自后台,而后台返回的是一个base64
位的图片。这就没办法使用wx.getImageInfo
了。
幸好,官方也提供了方法wx.getFileSystemManager()
const base64Url = '这是一张base64的图片';
const number = Math.random();
const filePath = `${wx.env.USER_DATA_PATH}/pic${number}.png`;
const buffer = wx.base64ToArrayBuffer(base64Url);
wx.getFileSystemManager().writeFile({
filePath,
data: buffer,
encoding: 'base64',
success: () => {
_self.qrcodeUrlLocal = filePath
}, fail: err => {
console.log(err)
}
})
这里需要强调的是不使用wx.base64ToArrayBuffer
也是没问题的,可能是因为官方文档里说基础库2.4.0
以后不维护这个接口了吧。总之,这样你的小程序码就变成了本地的图片了,可以画到canvas
上去了。
这样就可以生成一张完整的海报了。
请教一个问题
有个延伸的问题一直没有找到答案,如果哪位朋友有解,还望告知啊!
就是制作了圆形头像使用了ctx.clip()
,这样在它后面的绘制的元素就必须要在截取框内才能显示了,可是,如果一张海报里需要有多个圆形图的话,那应该如何操作呢?