使用canvas生成微信小程序海报
很多小程序为了更好的推广,都会有生成海报的功能。在多数时候,海报都是后端生成好,然后前端直接下载展示,让用户保存到本地就ok。不过有的时候也是前端自己去拿数据然后生成海报,供用户保存,恰好自己捣鼓过那么一下,所以在这里分享一下前端生成海报的方法。
说直接一点,所谓的生成海报,无非就是将多张图片合成一张,或者在图片上面加点文字之类的东西,例如商品价格,商品信息之类的。既然是要合成图片,那最先想到的当然就是canvas画布了。因为我公司的项目代码不方便往这上面贴,所以我就新建了一个小程序的项目,自然而然就有那么一点简化。
pages/index/index.wxml文件:
1 <view class="poster-wrap" wx:if="{{showCanvas}}"> 2 <canvas class="canvas-style" canvas-id="myCanvas"></canvas> 3 <view bindtap="savePoster" class="save-btn">保存到本地</view> 4 </view> 5 <button bindtap="clickCreate" size="mini" type="primary">点我生成海报</button>
pages/index/index.wxss文件:
canvas弹出层的宽高用的是px单位,而不是rpx,如果用rpx,那在绘制的时候就不太好计算了。因此这里最好以px为单位。到后面导出或者设置海报宽高时都会方便。而且那种海报图片的大小,基本上不会大小不一,一般都是定死的。
1 .poster-wrap { 2 width:240px; 3 height:430px; 4 position: fixed; 5 top:50%; 6 left:50%; 7 transform: translate(-50%,-50%); 8 z-index: 99; 9 } 10 .canvas-style { 11 width:100%; 12 height:400px; 13 } 14 .save-btn { 15 width:100%; 16 height:30px; 17 background:rgba(24, 41, 75, 1); 18 text-align: center; 19 line-height:30px; 20 color:#FFF; 21 }
pages/index/index.js文件:
1 2 import { 3 createPoster 4 } from "../../utils/util.js"; 5 Page({ 6 data: { 7 img: '', // 生成后的海报路径,保存到本地时可用,也可用于image标签展示 8 showCanvas: false, // 是否显示canvas 9 }, 10 11 // 保存海报 12 savePoster() { 13 wx.getSetting({ 14 success: (res) => { 15 // 判断是否有访问相册的权限,没有的情况下弹出框提示用户允许授权。 16 if (!res.authSetting['scope.writePhotosAlbum']) { 17 wx.authorize({ 18 scope: 'scope.writePhotosAlbum', 19 success: (aRes) => { 20 wx.saveImageToPhotosAlbum({ 21 filePath: this.data.img, 22 success: () => { 23 wx.showToast({ 24 title: '保存成功', 25 success:()=>{ 26 this.setData({ 27 showCanvas:false 28 }) 29 } 30 }) 31 }, 32 fail: () => { 33 wx.showToast({ 34 title: '保存失败', 35 icon: 'none' 36 }) 37 } 38 }) 39 } 40 }) 41 } else { 42 wx.saveImageToPhotosAlbum({ 43 filePath: this.data.img, 44 success: () => { 45 wx.showToast({ 46 title: '保存成功', 47 success:()=>{ 48 this.setData({ 49 showCanvas:false 50 }) 51 } 52 }) 53 }, 54 fail: () => { 55 wx.showToast({ 56 title: '保存失败', 57 icon: 'none' 58 }) 59 } 60 }) 61 } 62 } 63 }) 64 }, 65 66 // 点击创建海报 67 clickCreate() { 68 this.setData({ 69 showCanvas: true 70 }, () => { 71 // 创建海报 72 createPoster((res) => { 73 this.setData({ 74 img: res.tempFilePath 75 }) 76 }) 77 }) 78 79 }, 80 81 onLoad: function () { 82 83 }, 84 85 })
以上三个文件就是demo里面的代码了,因为代码不多,而且很好看懂,所以也没有什么好解释的。然后把生成海报的那个函数也贴出来:
utils/util.js文件:
1 /** 2 * @param {回调函数} fn 3 */ 4 export const createPoster = (fn) => { 5 wx.showLoading({ 6 title: "生成中..." 7 }) 8 const canvas = wx.createCanvasContext("myCanvas") 9 let cW = 240,cH = 400 10 // 背景图 11 canvas.drawImage("/images/timg (1).jpg",0,0,cW,cH) 12 // 商品名称 13 canvas.setFillStyle("#FFF") 14 canvas.setFontSize(12) 15 canvas.fillText("青少年T恤衫",30,30) 16 //商品图片 17 canvas.drawImage("/images/T-shirt.jpg",30,40,cW-60,cH-200) 18 // 商品价格 19 canvas.setFillStyle("red") 20 canvas.fillText("¥102",cW/2-20,cH-140) 21 // 商品介绍 22 canvas.setFillStyle("#FFF") 23 canvas.fillText("这是一件很好看的T恤衫,快来买咯!",20,cH-120) 24 canvas.setTextAlign("center") 25 // 用户头像 26 canvas.save() 27 canvas.arc(35,325,30,0,2*Math.PI) 28 canvas.clip() 29 canvas.drawImage("/images/avatar.jpg",5,295,60,60) 30 canvas.restore() 31 // 提示文字 32 canvas.setFontSize(12) 33 canvas.setFillStyle("#FFF") 34 canvas.setTextAlign("center") 35 canvas.fillText("长按识别二维码",115,330) 36 // 二维码 37 canvas.save() 38 canvas.arc(195,325,35,0,2*Math.PI) 39 canvas.clip() 40 canvas.drawImage("/images/baidu.png",160,290,70,70) 41 canvas.restore() 42 43 // 开始绘制 44 canvas.draw(true,()=>{ 45 // 将画布中的内容导出为图片 46 wx.canvasToTempFilePath({ 47 canvasId: "myCanvas", 48 destWidth:cW*3, // 导出后的图片宽度 49 destHeight:cH*3, // 导出后的图片高度 50 success:(res)=>{ 51 wx.hideLoading() 52 // 通过回调函数返回生成后的图片 53 fn && fn(res) 54 } 55 }) 56 }) 57 }
在绘制用户头像的时候,先调用save保存了一下画布的状态,然后调用arc绘制了一个x坐标35,y坐标325,半径30的圆形,clip函数调用后之后绘制的东西都会被限制在该圆圈之内,多数海报上的用户头像都是圆形的,因此我这里也搞成圆形的。
clip之后调用drawImage画了一张头像,头像的位置应该是刚好覆盖掉圆圈,那自然就形成了圆形的头像,所以,头像的x和y坐标以及大小(直径)应该先和圆圈保持一致,因此这里计算头像x和y两个点的位置为:x和y分别减掉圆圈的半径,那样头像就刚好覆盖掉圆圈。在完了以后,再把状态恢复回去,也就是调用restore函数,方便之后的内容绘制。
我这里绘制那些东西的时候,比较简单粗暴,一步一步绘制的。或许绘制的代码可能有点臃肿了,我这里用的是测试号,从网上找的图片无法通过小程序的api去下载,需要配域名,测试号没法配,所以我就直接把用到的几张图片直接下载放到了images文件夹。正常情况下是需要通过wx.downLoadFile去先下载图片的,我这里被限制了,所以简化了很多。在真正小程序代码里面去搞这些的时候,其实背景图很重要,因为可以少画一些不必要的东西,而只需要关注变动的,比如价格,商品图片,介绍,二维码以及用户头像,其余的不变的,按理说都应该是由设计弄一张产品看得上的背景图片,而海报上不变的东西,像什么分割线,背景图案什么的,都应该是背景图的一部分,我们只需要在背景图上去把那些变动的商品用户相关的东西给合上去就好。
在绘制好了之后,调用canvasToTempFilePath函数导出画布中的内容,导出的形式是一张图片,默认为png格式,小程序里面只有两种格式可选,png和jpg。
这里有个需要注意的地方,destWidth和destHeight是导出后的图片大小,这里之所以在canvas背景图宽高的基础上乘以3,是为了解决导出后图片模糊失贞的问题,出现图片模糊的原因在于canvas绘制的时候是逻辑像素,而destWidth和destHeight导出宽高时是物理像素。物理像素和逻辑像素的资料有很多,百度随便搜一大把。
在导出图片成功之后,会执行success回调,不过这里的数据并不是在这里直接取的,而是需要暴露给外面,因此这里通过回调函数的方式去做。
其实canvas是一个很强悍的东西,绘制这么一些静态的海报连冰山一角都算不上,我觉得应该花时间去深度了解一下。
这里只是提供了一种方式,仅供参考。所以对于样式以及图片就显得很随意了。如图: