小程序 canvas 2d 手写签名
在最近的小程序项目中,设计师又给我出难题了,需要在小程序里面实现一个手写签名功能,完成后将签名生成文件,上传的服务端去。手写签名不难,难在居然要我生成 svg,还有将签名功能搞得和画板一样,我是不能忍,直呼搞不了。
安卓版手写签名 github.com/silencefly9…
签名功能实际在我之前的安卓开发中用过,支持笔画的前进、后退、全清除以及图片的导出,真就快做到 生成 SVG 的底部了,要不是我不懂 SVG ,就手撕一个根据 path 路径生成 svg 的代码了。
扯远了,小程序和安卓原生比不得,我就只弄了清除和导出功能。
先看看效果
这个是使用二阶贝塞尔曲线优化后的效果,曲线很流畅。
这个是没使用贝塞尔曲线优化的效果,笔画很粗糙。
性能和适配
一开始我用的旧版本 canvas 去绘制签名,因为网上有个县城的案例,后面发现用起来卡的一批,而且在有些手机上导出图片还会有没数据问题,根本没法忍。
于是自己学了学 canvas 2d 的用法,结合贝塞尔曲线,做了个性能和流畅度都还可以的手写签名页面。
看看代码
- WXML
<canvas class="canvas" type="2d" id="myCanvas" bindtouchstart="bindtouchstart" bindtouchmove="bindtouchmove" />
<cover-view class="row button-container">
<cover-view class="flex1 button " bindtap='clear'>
清除
</cover-view>
<cover-view class="flex1 button margin-left-12" bindtap='export'>
提交
</cover-view>
</cover-view>
这里就是放了一个 canvas,加上使用 cover-view做的两个按钮。
- JSON
{
"usingComponents": {},
"pageOrientation": "landscape",
"navigationBarTitleText": "手写签名",
"enablePullDownRefresh": false
}
这里设置页面的横向显示,并禁用了下拉刷新,页面横向显示的时候,导出的图片也是横向的,所以并不需要多考虑。
- WXSS
.canvas {
height: 100vh;
width: 100vw;
}
.button-container {
width: 150rpx;
height: 30rpx;
position: absolute;
top: 16rpx;
right: 16rpx;
}
.button {
background: white;
color: #616161FF;
box-sizing: border-box;
border-radius: 20rpx;
line-height: 30rpx;
text-align: center;
border: 1px solid #C9C9C9;
}
这里 canvas 的宽高设置成了屏幕的宽高,使用单位 vh 和 vw,其他没什么说的。
- Page.js
var app = getApp()
Page({
data: {
canvas: null,
context: null,
// 前一点
preX: 0,
preY: 0
},
onShow: function () {
const that = this;
// 获取画布
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({
node: true,
size: true
})
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr
ctx.scale(dpr, dpr)
// 设置笔触信息
ctx.strokeStyle = '#000000'
ctx.lineWidth = 2
ctx.font = "20px Arial"
that.setData({
canvas: canvas,
context: ctx
})
})
},
/**记录开始点 */
bindtouchstart: function (e) {
let context = this.data.context
let curX = e.changedTouches[0].x
let curY = e.changedTouches[0].y
context.beginPath()
context.moveTo(curX, curY)
this.data.preX = curX
this.data.preY = curY
},
/**记录移动点,刷新绘制 */
bindtouchmove: function (e) {
let context = this.data.context
let preX = this.data.preX
let preY = this.data.preY
let curX = e.changedTouches[0].x
let curY = e.changedTouches[0].y
let deltaX = Math.abs(preX - curX)
let deltaY = Math.abs(preY - curY)
// 相差大于3像素的时候作二阶贝塞尔曲线
if (deltaX >= 3 || deltaY >= 3) {
// 前后两点中心点
let centerX = (preX + curX) / 2
let centerY = (preY + curY) / 2
//这里以前一点作为控制点,中心点作为终点,起始点为上一次的中点,很流畅啊!
context.quadraticCurveTo(preX, preY, centerX, centerY);
context.stroke();
this.data.preX = curX
this.data.preY = curY
}
},
/**清空画布 */
clear: function () {
let context = this.data.context
let canvas = this.data.canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.strokeStyle = '#000000'
context.lineWidth = 2
context.font = "20px Arial"
},
/**导出图片 */
export: function () {
const that = this;
wx.showLoading({
title: '正在生成签名...',
})
wx.canvasToTempFilePath({
canvas: that.data.canvas,
success(res) {
wx.hideLoading()
app.log("tempFilePath:" + res.tempFilePath);
//上传图片
...
},
fail() {
wx.hideLoading()
wx.showToast({
title: '生成图片失败',
icon: 'none',
duration: 1000
})
}
})
},
})
这里代码也好理解,一个是记录笔触开始,一个是在笔触移动的时候绘制线条,一个是清除画布,一个是导出图片。
绘制曲线
绘制曲线包含两部分,第一部分是获取开始点,开始画笔路径,移动到开始点,并将开始点记录到全局变量中。
第二部分是当手指移动的时候产生一系列点,将这一系列点连起来。这里用到了二阶贝塞尔曲线,一系列点中,以上两点的中心点为起始点,上一点作为控制点,本次最近两点的中心点作为终点,这样绘制出来的曲线很流畅,有预测曲线方向的作用。
清除画布
清除画布使用了 clearRect 函数,注意重新设置笔触信息。
导出图片
千万别被网上成千上万的旧版本的 canvas的 api 干扰了,当你使用 canvas 2d的时候根本就没有 draw 方法了,当然新的方法时要把 canvas 对象传进去。
上传图片
最后得到的图片时一个临时路径,保存在 res.tempFilePath 中,可以直接在 image 组件中使用,当然要上传的话也可以,上传功能看后台接口吧。
结语
经过 canvas 2d 和贝塞尔曲线的优化,我这手写签名虽然还是比较简陋,但是性能上和流畅度上真的比网上绝大部分博客好得多吧。
end
完美撒花
转载自:https://juejin.cn/post/7222897190698549308