目录javascript
目标:利用canvas画布生成社团活动的海报图片,便于社团活动宣传以及对小程序的推广。html
场景:用户A以为某个社团的活动很不错,所以点击“分享”按钮,生成一个该活动的海报图片,接着,用户A把该图片发到某个群或者朋友圈进行传播,用户B看到该图片后对这个活动蛮感兴趣,经过长按识别图片中的小程序码,可以进入“北航社团帮”小程序的相应活动详情页。java
UI设计:咱们将海报内容需求进行了详细描述后,联系了社联宣传部帮忙制做了海报原型的ps图,即本人将按照这个目标去生成海报:canvas
(附:社联设计的长宽比不大合适,过长,本人将进行调整)小程序
首先须要在wxml中使用canvas组件,注意须要把组件位置设置到屏幕以外,由于canvas画布画出来很丑。(虽然保存为图片的时候很好看)微信
<view> <canvas style="width: 375px;height: 690px;position:fixed;top:9999px" canvas-id="mycanvas" /> <!-- canvas画布画出来很丑,不能用让用户看见 --> </view>
接着须要在js中编写canvas绘制函数ide
//在canvas上进行具体绘画的函数 drawCanvas: function () { console.log("开始draw convas") var context = wx.createCanvasContext('mycanvas'); var greycolor = '#969696'; var maincolor = '#eda874'; //0.绘制背景图片和原竖版海报 console.log("在画海报时,原海报下载的临时地址为:") console.log(_this.data.poster_old) context.drawImage(_this.data.poster_old, 69, 120, 237, 333); context.drawImage("/images/bg4.png", 0, 0, 375, 690); context.save(); //1.绘制头像 var radius = 20; var center_x = 79; var center_y = 30; context.arc(center_x, center_y, radius, 0, 2 * Math.PI) //画出圆 context.clip(); //裁剪上面的圆形 console.log("在画海报时,原头像下载的临时地址为:") console.log(_this.data.touxiang) context.drawImage(_this.data.touxiang, center_x - radius, center_y - radius, radius * 2, radius * 2); // 在刚刚裁剪的园上画图 context.restore(); //2.绘制昵称 console.log(_this.data.userInfo) var nickname = _this.data.userInfo.nickname.replace(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/'/g, "'").replace(/ /g, " ").replace(/ /g, " ") //特殊字符转义 // nickname = "分解分解红红火火恍恍惚惚a"; var max_nickname_width = 120;//一个汉字的宽度为10 context.font = 'normal 10px sans-serif'; var nickname_len_by_10 = context.measureText(nickname).width; if (nickname_len_by_10 > max_nickname_width) {//昵称宽度大于最大显示宽度,则只取前8个字符 console.log("昵称太长,cut"); nickname = nickname.slice(0, 12) + '...'; } var width = 17;//昵称的字号 var position_x_name = 60; var position_y_name = 75; context.setFontSize(width); context.setTextAlign('left'); context.setFillStyle(greycolor); context.fillText(nickname, position_x_name, position_y_name); //3.绘制活动标题 var actname = _this.data.activity_dict.name; // actname = "分解分解红红火火恍恍惚惚什么什么分解分解红红火火恍恍惚惚什么什么"; var max_actname_width = 130; context.font = 'normal 10px sans-serif'; var actname_len_by_10 = context.measureText(actname).width; if (actname_len_by_10 > max_actname_width) {//昵称宽度大于最大显示宽度,则只取前8个字符 console.log("活动名称太长,cut"); actname = actname.slice(0, 13) + '...'; } var width = 19;//活动名称的字号 var position_x_actname = 62; var position_y_actname = 495; context.font = 'normal bold 10px sans-serif'; context.setFontSize(width); context.setTextAlign('left'); context.setFillStyle(maincolor); context.fillText(actname, position_x_actname, position_y_actname); //4.绘制活动社团名称、活动地点 //4.1.关于内容 var club_const = "社团名称"; var place_const = "活动地点"; var club = _this.data.club_info.name; var place = _this.data.activity_dict.place; var max_width = 130;//你体会一下 context.font = 'normal 10px sans-serif'; // club = "解分解红红火火恍恍惚惚什么什么解分解红红火火恍恍惚惚什么什么"; // place = "分解分解红红火火恍恍惚惚什"; var club_by_10 = context.measureText(club).width; if (club_by_10 > max_width) {//宽度大于最大显示宽度,则只取前8个字符 console.log("社团名称太长,cut"); club = club.slice(0, 12) + '...'; } var place_by_10 = context.measureText(place).width; if (place_by_10 > max_width) {//宽度大于最大显示宽度,则只取前8个字符 console.log("地点太长,cut"); place = place.slice(0, 12) + '...'; } //4.2.关于样式 var start_y = 527; var x = 64; var dis = 20;//"行间距" //4.3.画它 context.setTextAlign('left'); context.font = 'normal 12px sans-serif'; context.setFillStyle(maincolor); context.fillText(club_const, x, start_y); context.setFillStyle(greycolor); context.fillText(club, x, start_y + dis); context.setFillStyle(maincolor); context.fillText(place_const, x, start_y + dis * 2); context.setFillStyle(greycolor); context.fillText(place, x, start_y + dis * 3); //5.绘制活动时间相关信息 //5.1.关于内容 var start_time = _this.data.activity_dict.start_time;//形如"2019-05-26 18:00 周日" var year = start_time.slice(0, 4); var month = start_time.slice(5, 7); var day = start_time.slice(8, 10); var hour_min_start = start_time.slice(11, 16); var hour_min_end = "(´-ω-`)";// (´-ω-`) o(≧v≦)o (≧v≦) var end_time = _this.data.activity_dict.end_time; var act_in_one_day = (start_time.slice(0, 10) == end_time.slice(0, 10));//判断该活动是否在一天内结束 console.log("是否在一天内结束活动:") console.log(act_in_one_day); //仅当且仅当该活动end_time不为空,且该活动在一天内结束时,才显示活动结束时间,不然显示颜表情 if (end_time != "" && act_in_one_day) { hour_min_end = end_time.slice(11, 16); } //5.2.画它 // context.setFillStyle(maincolor);//为何要在下面每一个地方都放一个,由于真机第一次生成时有点问题 context.setTextAlign('left'); context.setFillStyle(maincolor); context.font = 'normal bold 25px sans-serif';//这个字体还行,不换了... context.fillText(month, 238, 548); context.setFillStyle(maincolor); context.font = 'normal bold 25px sans-serif'; context.fillText(day, 238, 589); context.setFillStyle(maincolor); context.setTextAlign('right'); context.font = 'normal 11px sans-serif'; context.fillText(year, 316, 516); context.setFillStyle(maincolor); context.setTextAlign('right'); context.font = 'normal 14px sans-serif'; context.fillText(hour_min_start, 316, 556); context.setFillStyle(maincolor); context.setTextAlign('right'); context.font = 'normal 14px sans-serif'; context.fillText(hour_min_end, 316, 585); //6.绘制小程序码 context.drawImage(_this.data.minicode, 255, 600, 60, 60); //7.将canvas生成好的图片下载到临时文件夹 console.log("画好了") context.draw(false, function () { wx.canvasToTempFilePath({ canvasId: 'mycanvas', success: function (res) { var tempFilePath = res.tempFilePath; _this.setData({ imagePath: tempFilePath, hide_poster: false }); console.log("图片下载到临时文件夹了") }, fail: function (res) { console.log(res); } }); }); }
其中涉及到许多小程序中canvas画布的接口,读者请自行在微信官方文档中查看。比较重要的几点将在下文说明。函数
下面先展现一下代码中所用到的固定图片"/images/bg4.png",是本人利用PS生成的,是png图片,中间给海报图片留了空:(论善用PS的重要性)post
下面展现下最后的海报生成效果:测试
点击活动详情页面的“分享”按钮:
就会生成活动的海报:
点击“保存相册”,便可将图片保存到相册。保存到相册的图片以下:
//3.绘制原竖版海报 //3.1.绘制圆角矩形。首先定义圆角矩形的左上角点坐标,圆的半径,矩形的长宽。 var x = 70; var y = 122; var r = 8; var w = 235; var h = 329; context.beginPath() // 由于边缘描边存在锯齿,最好指定使用 transparent 填充 context.setFillStyle('transparent');// 这里是使用 fill 仍是 stroke均可以,二选一便可 // 左上角,border-top context.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5) context.moveTo(x + r, y) context.lineTo(x + w - r, y) context.lineTo(x + w, y + r) // 右上角,border-right context.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2) context.lineTo(x + w, y + h - r) context.lineTo(x + w - r, y + h) // 右下角,border-bottom context.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5) context.lineTo(x + r, y + h) context.lineTo(x, y + h - r) // 左下角,border-left context.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI) context.lineTo(x, y + r) context.lineTo(x + r, y) // 这里是使用 fill 仍是 stroke均可以,二选一便可,可是须要与上面对应 context.fill() context.closePath() //关闭路径 //3.2.裁剪圆角矩形,绘制竖版海报 context.clip(); //裁剪上面的圆角矩形 console.log("在画海报时,原海报下载的临时地址为:") console.log(_this.data.poster_old) context.drawImage(_this.data.poster_old, x, y, w, h); // 在刚刚裁剪的园上画图 context.restore();
最后在模拟器上确实实现了想要的效果,可是真机调试时,却发现没法看到竖版海报,只能看到圆角矩形。通过了坚苦卓绝的debug,仍然没有找到缘由(没有试出缘由),抱着结果比过程更重要的决心,我使用PS奇淫巧技地实现了想要的效果。
也就是:先将竖版海报图片放在底层,而后用顶层图片进行覆盖,顶层图片是经过PS留下中间透明的圆角矩形,从而实现对竖版海报图片的圆角矩形裁剪效果。
canvas绘图许多地方须要定位,若是将全部位置都进行硬编码的话,不管是在开发仍是后期调整的过程当中,都会难以调整,对此,笔者倡导适当进行硬编码,一旦你发现某个位置的坐标须要使用计算器计算/心算时,就代表你差很少应该使用变量来进行软编码的。
以对过长的昵称进行截取为例:
var max_nickname_width = 120;//一个汉字的宽度为10 context.font = 'normal 10px sans-serif'; var nickname_len_by_10 = context.measureText(nickname).width; if (nickname_len_by_10 > max_nickname_width) {//昵称宽度大于最大显示宽度,则只取前8个字符 console.log("昵称太长,cut"); nickname = nickname.slice(0, 12) + '...'; }
首先,设置字体大小为10px,而后使用measureText函数(具体自行查看文档),便可获得所测量文字在10px字体下的宽度(一个汉字的宽度是10px,字母和标点符号会小一些)。
接着,设置max_width为120,代表昵称最长应是120px,也就是12个汉字的宽度,若是昵称过长将会进行截取,截取是经过slice函数进行。
在真机测试时,发现首次生成的海报的字号不对,解决方案是:
在绘制文字filltext前,都设置一下文字的样式(大小、颜色、字体等)。
这点将在下一篇技术博客进行详解:http://www.javashuo.com/article/p-hydjefpt-gq.html