記:微信小程序實現橫屏手寫板&圖片正向保存

效果圖:

畫板實現:

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() {

  }
})

歡迎評論/私信交流~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章