微信小程序实现手写签名并旋转后保存 | 8月更文挑战

2,358 阅读4分钟

先说结论:IOS和IPAD端无法实现旋转后保存,可直接签名后保存,安卓端无影响,IPAD端性能比较差,笔记偶发性延迟。

ps:微信小程序开发文档是真心的烂,各种不说清楚,各种过时和不兼容接口不标明。

好了,废话不多说,让我们进入正题。

利用Canvas实现手写签名效果

鉴于这部分已经有很多的文章讲解了,且本人对Canvas了解的也不深,所以这部分就不过多介绍了,最后会附上完整的代码,说下里需要注意的点吧。效果图如下:

image

旋转图片后保存

根据实现的效果图我们可以发现签名生成的图片是一张纵向的图,如果我们直接保存,呈现时会不太美观和合理,所以我们需要将生成的图片旋转后再保存。

又是一番面向搜索引擎编程,整理出大致的思路是这样子的,首先在页面上添加一个不在屏幕内的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什么事啊,还有就是如果OffscreenCanvascanvasToTempFilePath接口不兼容,你倒是在文档里说明下啊,你这不是害人么?哎,终究还是我错付了啊。

方案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')
  })