小程序分享到朋友圈只能使用小程序码海报来实现, 生成小程序码的方式有两种, 一种是使用后端方式, 一种是使用小程序自带的 canvas 生成; 后端的方式开发难度大, 由于生成图片耗用内存比较大对服务端也是不小的压力; 所以使用小程序的 canvas 是一个不错的选择, 但由于 canvas 水比较深, 坑比较多, 还有不同海报需要重现写渲染流程, 导致代码冗余难以维护, 加上不同设备版本的情况不一样, 因此小程序海报生成组件的需求十分迫切.
在实际开发中, 我发现海报中的元素无非一下几种, 只要实现这几种, 就可以通过一份配置文件生成各种各样的海报了.
海报中的元素分类
要解决的问题
单位问题
canvas 隐藏问题
圆角矩形, 圆角图片
多段文字
超长文字和多行文字缩略问题
矩形包含文字
多个元素间的层级问题
图片尺寸和渲染尺寸不一致问题
canvas 转图片
IOS 6.6.7 clip 问题
关于获取 canvas 实例
单位问题
canvas 绘制使用的是 px 单位, 但不同设备的 px 是需要换算的, 所以在组件中统一使用 rpx 单位, 这里就涉及到单位怎么换算问题.
通过 wx.getSystemInfoSync 获取设备屏幕尺寸, 从而得到比例, 进而做转换, 代码如下:
- 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, 所以我们必须把 canvas 隐藏起来, 一开始想到的是使用 display:none; 但这样在转化成图片时会空白, 所以这个是行不通的, 所以只能控制 canvas 的绝对定位, 将其移出可视界面, 代码如下:
- .canvas.pro {
- position: absolute;
- bottom: 0;
- left: -9999rpx;
- }
圆角矩形, 圆角图片
由于 canvas 没有提供现成的圆角 api, 所以我们只能手工画啦, 实际上圆角矩形就是由 4 条线 (黄色) 和 4 个圆弧 (红色) 组成的, 如下:
圆弧可以使用 canvasContext.arcTo 这个 api 实现, 这个 api 的入参由两个控制点一个半径组成, 对应上图的示例
canvasContext.arcTo(x1, y1, x2, y2, r)
接下来我们就可以非常轻松的写出生成圆角矩形的函数啦
- /**
- * 画圆角矩形
- */
- _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.getImageInfoApi 获取源图的尺寸;
canvas 转图片
在 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 问题
在 IOS 6.6.7 版本中 clip 方法连续裁剪图片时, 只有第一张有效, 这是微信的 bug, 官方也证实了( https://developers.weixin.qq.com/community/develop/buglist )
关于获取 canvas 实例
我们可以使用
wx.createCanvasContext
获取小程序实例, 但在组件中使用切记第二个参数需要带上 this, 如下
this.ctx = wx.createCanvasContext('canvasid', this);
如何使用组件
https://github.com/jasondu/wxa-plugin-canvas
来源: https://segmentfault.com/a/1190000016121303