记:微信小程序实现横屏手写板&图片正向保存

本文详细介绍了一款基于微信小程序的手绘签名组件实现过程,包括画布操作、图片旋转及保存技巧,解决手绘签名在移动端应用中的常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果图:

画板实现:

wxml

<view class="row-wrapper">
  <view class="column-container">
    <view class="gray-bg round-rect rotate-90" style="margin-bottom: 160rpx;" catchtap="cleardraw">重置</view>
    <view class="pink-bg round-rect margin-top-30 rotate-90" catchtap="submit">提交</view>
  </view>
  <view class="paint-board">
    <text class="placeholder-tips rotate-90" wx:if="{{!isDrawing}}">请手写签名</text>
    <canvas class="paint-board-canvas" disable-scroll="true" canvas-id="board" bindtouchstart="startDraw" bindtouchmove="onDraw" bindtouchcancel="endDraw"></canvas>
  </view>
  <canvas class="tmp-canvas" canvas-id="tmp_board" disable-scroll="true"></canvas>
</view>

疑问点:为什么这里要加一个tmp_board?——后续会提到。

wxss:

.row-wrapper {
  width: 100%;
  height: 95vh;
  margin: 30rpx 0;
  overflow: hidden;
  display: flex;
  align-content: center;
  flex-direction: row;
  justify-content: center;
}

.column-container {
  display: flex;
  margin: 30rpx 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 20%;
}

.paint-board {
  /* width: 100%;
  height: 95vh; */
  border: 1px dashed #bbb;
  position: relative;
  flex: 5;
  overflow: hidden;
  box-sizing: border-box;
  margin-right: 20rpx;
}

.paint-board-canvas {
  width: 100%;
  height: 95vh;
}

.pink-bg {
  background: #F2B9B9;
}

.round-rect {
  border: 1px solid #bbb;
  width: 200rpx;
  height: 60rpx;
  min-height: 60rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-size: 28rpx;
  margin-bottom: 60rpx;
  border-radius: 4px;
}

.margin-top-30 {
  margin-top: 60rpx;
}

.margin-left-30 {
  margin-left: 60rpx;
}

.margin-right-30 {
  margin-right: 60rpx;
}

.placeholder-tips {
  width: 20px;
  font-size: 16px;
  font-weight: 500;
  color: black;
  position: absolute;
  left: 50%;
  margin-left: -10px;
  top: 50%;
  margin-top: -45px;
}

.row-container {
  display: flex;
  justify-content: center;
}

.gray-bg {
  background: gray;
}

.rotate-90 {
  transform: rotate(90deg)
}

.margin-bottom-30 {
  margin-bottom: 60rpx;
}

.tmp-canvas {
  position: absolute;
  width: 360px;
  height: 180px;
  top: -200px;
  left: -400px;
}

page {
  background: #fbfbfb;
  height: auto;
  overflow: hidden;
  position: relative;
}

小坑:

这里在wxml中,canvas一定不能用catchTouchXXX,不然会导致画笔在手写的时候,与手指触碰点之间有一定的偏移。

接下来主要讲讲,在js中主要用到的方法。

由微信小程序官方api可知,wx.canvasToTempFilePath可将画布内容转换为临时文件。

转换为临时文件后,我们即可通过res.tempFilePath获取到画布素材的文件,从而进行服务器上传/本地保存。

由此,我们这里的步骤即为:

  1. 通过bindTouchXXX等方法,定制画布内容
  2. 通过wx.canvasToTempFilePath的api来转换画布并获取临时文件
  3. 将临时文件保存到相册

坑点:

这里的步骤看似没有问题,但是当实践起来会发现,在保存到相册时,图片被旋转了(由于横屏编写的原因),这对于用户的体验肯定是不好的,而且上传到服务器时,也没理由让后台同学帮我们批处理这些手写图片。因此,还得加上一个步骤——旋转图片。

  1. 通过bindTouchXXX等方法,定制画布内容
  2. 通过wx.canvasToTempFilePath的api来转换画布并获取临时文件
  3. 通过canvas的api,translate&rotate来实现图片定点定向旋转
  4. 将临时文件保存到相册

回应开头:这里就需要用到tmp_board的canvas了,由于在原始画布转换为临时图片时,得到的临时图片还未转换,因此需要将转换后的图片重新画在一个看不到的画布上,之后再调用一次wx.canvasToTempFilePath来生成最终的图片。

Talk is cheap, show me the code!

// pages/user/sign/sign.js
const dateUtils = require('../../../assets/dateutils.js')
const app = getApp()
var ctx = null
var arrx = []
var arry = []
var arrz = []
var canvasw = 600
var canvash = 300
var isButtonDown = false

Page({

  /**
   * 页面的初始数据
   */
  data: {
    isDrawing: false,
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    var query = wx.createSelectorQuery()
    query.select('.paint-board-canvas').boundingClientRect(rect => {
      canvasw = rect.width
      canvash = rect.height
      console.log(canvasw + '-' + canvash)
    }).exec()
    ctx = wx.createCanvasContext("board")
    ctx.beginPath()
    ctx.setStrokeStyle("#000000")
    ctx.setLineWidth(4)
    ctx.setLineCap("round")
    ctx.setLineJoin("round")
    console.log(options.imgType)
    this.setData({
      ctx: ctx
    })
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function() {

  },

  startDraw: function(e) {
    // console.log(e)
    this.setData({
      isDrawing: true
    })
    isButtonDown = true
    arrz.push(0)
    arrx.push(e.changedTouches[0].x)
    arry.push(e.changedTouches[0].y)
  },

  onDraw: function(e) {
    // console.log(e)
    if (isButtonDown) {
      arrz.push(1)
      arrx.push(e.changedTouches[0].x)
      arry.push(e.changedTouches[0].y)
    }
    for (var i = 0; i < arrx.length; i++) {
      if (arrz[i] == 0) {
        ctx.moveTo(arrx[i], arry[i])
      } else {
        ctx.lineTo(arrx[i], arry[i])
      };

    };
    ctx.clearRect(0, 0, canvasw, canvash);

    ctx.setStrokeStyle('#000000');
    ctx.setLineWidth(4);
    ctx.setLineCap('round');
    ctx.setLineJoin('round');
    ctx.stroke();

    ctx.draw(false);

  },

  endDraw: function(e) {
    isButtonDown = false
  },

  cleardraw: function (e) {
    //清除画布
    arrx = [];
    arry = [];
    arrz = [];
    ctx.clearRect(0, 0, canvasw, canvash);
    ctx.draw(true);
    this.setData({
      isDrawing: false
    })
  },

  submit: function(e) {
    if (!this.data.isDrawing) {
      wx.showToast({
        title: '请先完成签名',
        icon: 'none'
      })
    } else {
      wx.showLoading({
        title: '提交中...',
      })
      this.saveTmpImg()
    }
  },

  saveTmpImg: function() {
    // 这里是转换id为board的canvas,即未旋转前的图片
    wx.canvasToTempFilePath({
      canvasId: 'board',
      fileType: 'png',
      destWidth: '300',
      destHeight: '600',
      quality: 1, //图片质量
      success: (res) => {
        this.setRotateImage(res.tempFilePath)
      }
    })
  },

  setRotateImage: function(filePath) {
    console.log('rotate')
    // 这里就用到了tmp_board 即将转换后的图片画回到画布中 只是在页面中无感知而已
    let tmpCtx = wx.createCanvasContext('tmp_board', this)
    // 设置基准点 因为要转-90度 所以要将基准点设置为左下角
    tmpCtx.translate(0, 180)
    tmpCtx.rotate(270 * Math.PI / 180)
    // drawImage时 因为长宽不一 需要按原始图片的宽高比例设置来填充下面的后两个参数 如原始图片 300 * 600 则转过来时需要保持宽比高短 如下面的180 * 360
    tmpCtx.drawImage(filePath, 0, 0, 180, 360)
    tmpCtx.draw(false)
    // 这里是确保能draw完再保存
    setTimeout(() => {
      this.uploadCanvasImg('tmp_board', 1)
    }, 1000)
  },

  //上传
  uploadCanvasImg: function(canvasId, quality) {
    let token = app.globalData.token
    if (token == undefined || token == '') {
      token = wx.getStorageSync('token')
    }
    wx.canvasToTempFilePath({
      canvasId: canvasId,
      fileType: 'png',
      destWidth: '120',
      destHeight: '52',
      quality: quality, //图片质量
      success: (res) => {
        // console.log(res)
        let filePath = res.tempFilePath
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success: (res) => {
            wx.showToast({
              title: '已保存到相册',
              duration: 2000
            })
          }
        })
        // 可做上传操作
      }
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function() {
    this.cleardraw()
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function() {

  }
})

欢迎评论/私信交流~

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值