先说结论:IOS和IPAD端无法实现旋转后保存,可直接签名后保存,安卓端无影响,IPAD端性能比较差,笔记偶发性延迟。
ps:微信小程序开发文档是真心的烂,各种不说清楚,各种过时和不兼容接口不标明。
好了,废话不多说,让我们进入正题。
利用Canvas实现手写签名效果
鉴于这部分已经有很多的文章讲解了,且本人对Canvas了解的也不深,所以这部分就不过多介绍了,最后会附上完整的代码,说下里需要注意的点吧。效果图如下:
旋转图片后保存
根据实现的效果图我们可以发现签名生成的图片是一张纵向的图,如果我们直接保存,呈现时会不太美观和合理,所以我们需要将生成的图片旋转后再保存。
又是一番面向搜索引擎编程,整理出大致的思路是这样子的,首先在页面上添加一个不在屏幕内的Canvas,然后再保存签名的图片时,将生成的图片旋转画到不在屏幕内的Canvas上,最后调用wx.canvasToTempFilePath
接口将不在屏幕内的Canvas保存成图片。有了思路后,我们就着手开始行动。
方案1:利用官方接口创建不在屏幕内的Cavans(走不通)
又是一番搜索,发现微信官方提供了一个专门的接口用来生成不在屏幕内的Canvas,当时的我还想到底是大厂啊,如此的贴心,谁知道还是我太年轻啊。
代码如下:
// 创建离屏 2D canvas 实例
const canvas = wx.createOffscreenCanvas({type: '2d', width: this.data.canvasHeight, height: this.data.canvasWidth})
// 获取 context。注意这里必须要与创建时的 type 一致
const context = canvas.getContext('2d')
// 画布旋转
context.rotate(-90 * Math.PI / 180);
// 创建一个图片
const image = canvas.createImage()
// 等待图片加载
await new Promise(resolve => {
image.onload = resolve
image.src = this.data.canvas.toDataURL('image/png', 1) // 原canvas生成的图片
})
context.drawImage(image, -width, 0, width, height)
wx.canvasToTempFilePath({
canvas: canvas,
fileType: 'jpg',
quality: 1, //图片质量
success(res) {
console.log(res)
console.log(res.tempFilePath, 'canvas生成图片地址')
wx.previewImage({
urls: [res.tempFilePath] //预览图片 数组
})
}
})
一番操作后,保存编译运行,发现死活不行,一致报如下错误:
Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLImageElement or SVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or OffscreenCanvas)'
根据错误信息,再结合我们的代码,可以猜想应该是我们的OffscreenCanvas
调用drawImage
时发生了错误,但是不应该啊,你这个API接口里的示例代码就是这样写的啊,难道示例代码都会有错,不会吧?那难度是我这个drawImage
写错了,于是我有一个字母一个字母的核对了下,发现也没错啊,这个时候我的内心已经接近于崩溃了。
冷静了一会后,突然想到我得先确定到底是哪里的错,于是我开始一部分一部分的将console.log
,最后发现不是drawImage
的问题,而是canvasToTempFilePath
接口的问题,我说微信啊,是它的错,你就报它的错啊,管人家drawImage
什么事啊,还有就是如果OffscreenCanvas
和canvasToTempFilePath
接口不兼容,你倒是在文档里说明下啊,你这不是害人么?哎,终究还是我错付了啊。
方案2:将Cavnas浮动定位到屏幕外
核心代码如下:
<view style="position:absolute;left:750rpx;top:0;">
<canvas type="2d" id="offCanvas" style="width: 100%; height: 100vh;"></canvas>
</view>
this.data.offCtx.rotate(-90 * Math.PI / 180);
const srcImg = this.data.offCanvas.createImage()
// 等待图片加载
// console.log(this.data.canvas.toDataURL('image/png', 1))
await new Promise(resolve => {
srcImg.onload = resolve
srcImg.src = this.data.canvas.toDataURL('image/png', 1) // 要加载的图片 url
})
let width = this.data.canvasWidth / this.data.dpr
let height = this.data.canvasHeight / this.data.dpr
this.data.offCtx.drawImage(srcImg, -width, 0, width, height)
wx.canvasToTempFilePath({
canvas: this.data.offCtx,
fileType: 'jpg',
quality: 1, //图片质量
success(res) {
console.log(res.tempFilePath, 'canvas生成图片地址')
wx.previewImage({
urls: [res.tempFilePath] //预览图片 数组
})
}
})
结语
微信代码片段:developers.weixin.qq.com/s/enuwnbmp7…
最后再说一个需要注意的点,新版Canvas是需要显示的设定宽和高的,但是如果你只是在Css中指定了宽和高,那么实际的Canvas宽和高还是默认的,也就是可绘制区域,这个时候我们需要在js代码中设定宽和高,代码如下:
// 因为Canvas支持是像素单位,所以这里我们需要获取到dpr来将参照的宽和高转化为实际的像素值
this.data.dpr = wx.getSystemInfoSync().pixelRatio
const query = wx.createSelectorQuery()
query
.select('.handCenter')
.boundingClientRect(rect => {
this.data.canvasWidth = rect.width * this.data.dpr
this.data.canvasHeight = rect.height * this.data.dpr
})
.exec()
const query1 = wx.createSelectorQuery()
query1
.select('#signCanvas')
.fields({ node: true, size: true })
.exec(res => {
const canvas = res[0].node
this.data.ctx = canvas.getContext('2d')
canvas.width = this.data.canvasWidth
canvas.height = this.data.canvasHeight
/* 将canvas背景设置为 白底,不设置 导出的canvas的背景为透明 */
// console.log(this, 'hahah');
this.data.canvas = canvas
this.data.ctx.scale(this.data.dpr, this.data.dpr)
this.setCanvasBg('#fff')
})