效果圖:
畫板實現:
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獲取到畫布素材的文件,從而進行服務器上傳/本地保存。
由此,我們這裏的步驟即爲:
- 通過bindTouchXXX等方法,定製畫布內容
- 通過wx.canvasToTempFilePath的api來轉換畫布並獲取臨時文件
- 將臨時文件保存到相冊
坑點:
這裏的步驟看似沒有問題,但是當實踐起來會發現,在保存到相冊時,圖片被旋轉了(由於橫屏編寫的原因),這對於用戶的體驗肯定是不好的,而且上傳到服務器時,也沒理由讓後臺同學幫我們批處理這些手寫圖片。因此,還得加上一個步驟——旋轉圖片。
- 通過bindTouchXXX等方法,定製畫布內容
- 通過wx.canvasToTempFilePath的api來轉換畫布並獲取臨時文件
- 通過canvas的api,translate&rotate來實現圖片定點定向旋轉
- 將臨時文件保存到相冊
迴應開頭:這裏就需要用到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() {
}
})
歡迎評論/私信交流~