https://github.com/jasondu/wx...javascript
小程序分享到朋友圈只能使用小程序码海报来实现,生成小程序码的方式有两种,一种是使用后端方式,一种是使用小程序自带的canvas生成;后端的方式开发难度大,因为生成图片耗用内存比较大对服务端也是不小的压力;因此使用小程序的canvas是一个不错的选择,但因为canvas水比较深,坑比较多,还有不一样海报须要重现写渲染流程,致使代码冗余难以维护,加上不一样设备版本的状况不同,所以小程序海报生成组件的需求十分迫切。css
在实际开发中,我发现海报中的元素无非一下几种,只要实现这几种,就能够经过一份配置文件生成各类各样的海报了。java
canvas绘制使用的是px单位,但不一样设备的px是须要换算的,因此在组件中统一使用rpx单位,这里就涉及到单位怎么换算问题。git
经过wx.getSystemInfoSync获取设备屏幕尺寸,从而获得比例,进而作转换,代码以下:github
const sysInfo = wx.getSystemInfoSync(); const screenWidth = sysInfo.screenWidth; this.factor = screenWidth / 750; // 获取比例 function toPx(rpx) { // rpx转px return rpx * this.factor; } function toRpx(px) { // px转rpx return px / this.factor; },
在绘制海报过程时,咱们不想让用户看到canvas,因此咱们必须把canvas隐藏起来,一开始想到的是使用display:none; 但这样在转化成图片时会空白,因此这个是行不通的,因此只能控制canvas的绝对定位,将其移出可视界面,代码以下:canvas
.canvas.pro { position: absolute; bottom: 0; left: -9999rpx; }
因为canvas没有提供现成的圆角api,因此咱们只能手工画啦,实际上圆角矩形就是由4条线(黄色)和4个圆弧(红色)组成的,以下:小程序
圆弧可使用canvasContext.arcTo这个api实现,这个api的入参由两个控制点一个半径组成,对应上图的示例后端
canvasContext.arcTo(x1, y1, x2, y2, r)
接下来咱们就能够很是轻松的写出生成圆角矩形的函数啦api
/** * 画圆角矩形 */ _drawRadiusRect(x, y, w, h, r) { const br = r / 2; this.ctx.beginPath(); this.ctx.moveTo(this.toPx(x + br), this.toPx(y)); // 移动到左上角的点 this.ctx.lineTo(this.toPx(x + w - br), this.toPx(y)); // 画上边的线 this.ctx.arcTo(this.toPx(x + w), this.toPx(y), this.toPx(x + w), this.toPx(y + br), this.toPx(br)); // 画右上角的弧 this.ctx.lineTo(this.toPx(x + w), this.toPx(y + h - br)); // 画右边的线 this.ctx.arcTo(this.toPx(x + w), this.toPx(y + h), this.toPx(x + w - br), this.toPx(y + h), this.toPx(br)); // 画右下角的弧 this.ctx.lineTo(this.toPx(x + br), this.toPx(y + h)); // 画下边的线 this.ctx.arcTo(this.toPx(x), this.toPx(y + h), this.toPx(x), this.toPx(y + h - br), this.toPx(br)); // 画左下角的弧 this.ctx.lineTo(this.toPx(x), this.toPx(y + br)); // 画左边的线 this.ctx.arcTo(this.toPx(x), this.toPx(y), this.toPx(x + br), this.toPx(y), this.toPx(br)); // 画左上角的弧 }
若是是画线框就使用this.ctx.stroke();
数组
若是是画色块就使用this.ctx.fill();
若是是圆角图片就使用
this.ctx.clip(); this.ctx.drawImage(***);
clip() 方法从原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则全部以后的绘图都会被限制在被剪切的区域内(不能访问画布上的其余区域)。能够在使用 clip() 方法前经过使用 save() 方法对当前画布区域进行保存,并在之后的任意时间对其进行恢复(经过 restore() 方法)。
若是是连续多段不一样格式的文字,若是让用户每段文字都指定坐标是不现实的,由于上一段文字的长度是不固定的,这里的解决方案是使用ctx.measureText
(基础库 1.9.90 开始支持)Api来计算一段文字的宽度,记住这里返回宽度的单位是px(坑),从而知道下一段文字的坐标。
设置文字的宽度,经过ctx.measureText
知道文字的宽度,若是超出设定的宽度,超出部分使用“...”代替;对于多行文字,经测试发现字体的高度大约等于字体大小,并提供lineHeight参数让用户能够自定义行高,这样咱们就能够知道下一行的y轴坐标了。
这个一样使用ctx.measureText
接口,从而控制矩形的宽度,固然这里用户还能够设置paddingLeft和paddingRight字段;
文字的垂直居中问题能够设置文字的基线对齐方式为middle(this.ctx.setTextBaseline('middle');
),设置文字的坐标为矩形的中线就能够了;水平居中this.ctx.setTextAlign('center');
;
因为canvas没有Api能够设置绘制元素的层级,只能是根据后绘制层级高于前面绘制的方式,因此须要用户传入zIndex字段,利用数组排序(Array.prototype.sort)后再根据顺序绘制。
绘制图片咱们使用ctx.drawImage()
API;
若是使用drawImage(dx, dy, dWidth, dHeight)
,图片会压缩尺寸以适应绘制的尺寸,图片会变形,以下图:
在基础库1.9.0起支持drawImage(sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
,sx和sy是源图像的矩形选择框左上角的坐标,sWidth和sHeight是源图像的矩形选择框的宽度和高度,以下图:
若是绘制尺寸比源图尺寸宽,那么绘制尺寸的宽度就等于源图宽度;反之,绘制尺寸比源图尺寸高,那么绘制尺寸的高度等于源图高度;
咱们能够经过wx.getImageInfo
Api获取源图的尺寸;
在canvas绘制完成后调用wx.canvasToTempFilePath
Api将canvas转为图片输出,这样须要注意,wx.canvasToTempFilePath
须要写在this.ctx.draw
的回调中,而且在组件中使用这个接口须要在第二个入参传入this(坑),以下
this.ctx.draw(false, () => { wx.canvasToTempFilePath({ canvasId: 'canvasid', success: (res) => { wx.hideLoading(); this.triggerEvent('success', res.tempFilePath); }, fail: (err) => { wx.hideLoading(); this.triggerEvent('fail', err); } }, this); });
在IOS 6.6.7版本中clip方法连续裁剪图片时,只有第一张有效,这是微信的bug,官方也证明了(https://developers.weixin.qq....)
咱们可使用wx.createCanvasContext
获取小程序实例,但在组件中使用切记第二个参数须要带上this,以下
this.ctx = wx.createCanvasContext('canvasid', this);