效果图:
画板实现:
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() {
}
})
欢迎评论/私信交流~