关于
原文地址:canvas在线签名插件
插件地址github:https://github.com/javascript-wei/canvas-sign
体验预览:canvas-sign
背景
最近临时接到一个需求,需要客户方在线签字然后保存到服务器,功能大致有撤销(也就是笔画的每一笔都需要支持撤销)、更改签字颜色、移动端和pc端都支持。因为紧急需求,有现成的插件当然是最好的,毕竟学会偷懒也是一门技巧(滑稽),临时找了一个jSignature第三方的签名插件,仔细研究了一下发现该插件依赖复杂,使用该插件还需要引入jQuery,因为整个项目都能没有引入jquery,插件源码也是ES5,甚至还是ES3,当时我就犹豫了,不能因为一个签名引入这么复杂的插件吧。
因为本次需求最终就只需要一张图片保存到后台,也不需数字证书验证部分之类的,对于喜欢动手(完全是被逼)的我来说,这完全可以自己写啊。因为需求是周五出的,然后下周二就要,看来周末又是一个悄悄加班的日子。
分析
签名是若干操作的集合,起于用户手写姓名,终止于签名图片上传,中间还包含图片的处理,比如说减少锯齿、撤销、预览等,除了canvas绝无二选。
手撕
从整个交互上看,开始绘制时候需要定义起始点touchstart(移动端开始)、mousedown(pc鼠标按下),为了完成绘制,还需要处理手指移动或鼠标移动,监听处理两个事件touchmove(移动端)、mousemove(PC端)。
const handleMove = (e) => {
this.creat({ x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 });
}
const handleDown = (e) => {
this.creat({ x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 });
}
constf fn = {
mousedown: handleDown,
mousemove: handleMove,
//移动端
touchmove: handleMove,
touchstart:handleDown,
}
画线
接下来划线部分,canvas原生有以下api提供画线:
- 开始路径(beginPath)
- 定位起点(moveTo)
- 移动画笔(lineTo)
- 绘制路径(stroke)
有了start和move事件,画线的思路就明确了很多,按下按钮时候我们需要重置画笔beginPath,并且移动画笔moveTo,因为按下即便不移动也算是一个完整的绘制过程:
const handleDown = (e) => {
//按下键时候重置画笔
this.ctx.beginPath();
//isMouseDown 判断是否按下按键已经就绪绘制
this.isMouseDown = true;
const position = { x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 };
this.ctx.moveTo(position.x,position.y)
this.creat({ x: position.x, y: position.y });
}
移动处理:
const handleMove = (e) => {
//判断pc端是否鼠标左键按下,移动端不需要做判断
if ((!this.isMouseDown || e.which != 1) && !mobile) return;
e = mobile ? e.touches[0] : e;
this.creat({ x: e.clientX - left + 0.5, y: e.clientY - top + 0.5 });
}
const creat = (position = { x, y })=> {
ctx.lineTo(position.x, position.y);
ctx.stroke();
}
以上代码中的left和top并非内置变量,它们分别表示着画布距屏幕左边和顶部的像素距离,主要用于将屏幕坐标点转换为画布坐标点。以下是一种获取方法:
const { left, top } = this.canvas.getBoundingClientRect();
设置
接下来是更改画笔设置,需要支持原生canvas画笔属性:
// 提供一个条件是否更改保存当前样式,用于导入json时避免和已有的ctx样式冲突
setLineStyle(style = {},isSaveLineStyle = true) {
const ctx = this.ctx;
const lineStyle = isObject(style)? { ...this.lineStyle, ...style } : this.lineStyle;
if(isSaveLineStyle){
this.lineStyle = lineStyle;
}
Object.keys(lineStyle).forEach(key => {
ctx[key] = lineStyle[key];
});
ctx.beginPath();
return this;
}
导出json
画线需要路径和画笔的配置,路径则一系列坐标的集合,而画笔配置则是setLineStyle时传入的各个属性。由此可以得到json数据结构:
const json = [{
lineStyle:{},
position:[]
}]
什么时候保存,从开始绘制到结束绘制处理json的情况有三种:开始绘制,绘制中,结束绘制。
开始绘制:鼠标按下时,也就是一个点,此时往json数组中psuh当前值。
绘制中:往数组的最后一条数据中的position添加移动点的信息。
结束绘制:不在往数组添加数据,此时保存当前的lineStyle的信息。
setJson(value, type) {
const dataJson = this.dataJson;
const jsonLength = dataJson.length - 1;
switch (type) {
case 'moving':
dataJson[jsonLength].position.push(value);
break;
case 'end':
dataJson[jsonLength].lineStyle = value;
break;
case "start":
dataJson.push(value);
break;
}
}
优化
为了让move更加流畅,可以考虑requestAnimationFrame优化:
const raf = window.requestAnimationFrame;
const move = raf?(e)=>{
raf(() => {
handleEvent.handleMove(e);
});
}:handleEvent.handleMove;
写到最后
本文介绍了在线签字实现的主要过程,核心代码可以在这里找到:canvas-sign。
本问就讨论这么多内容,大家有什么问题或好的想法欢迎在下方参与留言和评论.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。