JS实现移动端在线签协议功能


Posted in Javascript onAugust 22, 2019

在一个风和日丽的下午,刚准备下班,突然接到需求说要做一个在线签协议功能,当时心里想着不就百度一顿拷贝就完事了吗(因为我没用过canvas,所谓初生牛犊不怕虎 ),谁知做起来如此吃力,下面就来记录下历程。

协议模板

JS实现移动端在线签协议功能 

分析

如上图,需要做的就是做一个签字板可以在上面写字,写完后点击完成可以生成如上图的图片所示,把签好的字放到指定的位置。

做这个第一反应肯定就是使用canvas绘制路径

我的思路是:

一个字一个字写,每写一个字点一下记录,最后拼接,但想到用户体验问题就pass了这个思路。

最后的思路:一行可以写很多个字,可以让用户滑动canvas,一直写下去(因为协议模板最后还要抄写一段话)

canvas绘制路径--实现签名功能

JS实现移动端在线签协议功能 

<canvas id="canvas" style="top:0">您的手机不支持在线签署</canvas>

const canvasPaint = {};//定义一个全局对象,把canvas的各种状态存进去
canvasPaint.canvas = document.getElementById("canvas");
canvasPaint.ctx = document.getElementById("canvas").getContext("2d");
canvasPaint.ctx.lineCap = 'round';//让结束线帽呈现圆滑状
canvasPaint.ctx.lineJoin = 'round';//交汇时呈现圆滑状
canvasPaint.ctx.strokeWidth = 5;//描边宽度
canvasPaint.ctx.lineWidth = 5;//线条宽度

初始化好画布后,我们需要监听画布上的滑动事件

canvasPaint.canvas.addEventListener('touchstart', startEventHandler, {passive: false});
function startEventHandler(event) {
 event.preventDefault();
 canvasPaint.ctx.beginPath();//每次都是一个新路径,不写会和上个字的最后一笔连起来
 canvasPaint.canvas.addEventListener('touchmove', moveEventHandler, {passive: false});
 canvasPaint.canvas.addEventListener('touchend', endEventHandler, {passive: false});
}

passive: false 和 event.preventDefault() 这两个是绝配哦, event.preventDefault() 阻止默认行为,防止在画布上写字时触发了浏览器自带的下拉动作之类的。那 passive: false 是谷歌56版本后提出的新属性,设置为 false 就是告诉浏览器我有阻止默认行为的代码,刚开始不要给我滑动,你需要执行我的 event.preventDefault() 这句代码,如果设置为了 true ,浏览器会自动忽略这句代码,从而不能阻止成功,默认是 true ,所以这里就是坑之一了。

我们继续编写移动划线逻辑

function moveEventHandler(event) {
 event.preventDefault();
 var coverPos = canvasPaint.canvas.getBoundingClientRect();
 canvasPaint.mouseX = event.clientX - coverPos.left;
 canvasPaint.mouseY = event.clientY - coverPos.top;
 if (canvasPaint.canPaint) {//后续为拖动画布功能设置的状态
 canvasPaint.ctx.lineTo(//使用lineTo将移动过的坐标绘制成线
 canvasPaint.mouseX,
 canvasPaint.mouseY
 );
 canvasPaint.ctx.stroke();//绘制
 }
}
function endEventHandler(event) {
 event.preventDefault();
 //抬起手指时取消move和end事件的监听
 canvasPaint.canvas.removeEventListener('touchmove', moveEventHandler, false);
 canvasPaint.canvas.removeEventListener('touchend', endEventHandler, false);
}

canvas--清除屏幕功能

这个功能比较简单就一句话

function clearCanvas() {
 canvasPaint.ctx.clearRect(0, 0, canvasPaint.canvas.width, canvasPaint.canvas.height);
}

提交签名功能

首先需要将画布上的文字转换为img对象,然后使用drawImage绘制到协议上去

preLoadImg(['/assets/index/images/agree.jpg', canvasPaint.canvas.toDataURL()], result);
//agree.jpg为协议名,canvasPaint.canvas.toDataURL()就是签好的字转换为base64的结果
function preLoadImg(source, callBack, args) {
 var pr = [];
 source.forEach(url => {
 var p = loadImage(url)
 .then(function (img) {
 return img;
 })
 .catch(function (err) {
 console.log(err);
 });
 pr.push(p);
 });

 Promise.all(pr)
 .then(function (imgArray) {
 callBack(imgArray, args);
 });

}
function loadImage(url) {
 return new Promise((resolve, reject) => {
 var img = new Image();
 img.onload = function () {
 resolve(img);
 };
 img.onerror = reject;
 img.src = url;
 });
}

由于img赋值src是异步的,我们必须要一个完整的image对象,所以我们使用promise包装,使得我们所有图片都转换完之后再将结果传入回调函数(result)中

function result(imgArr) {
 drawName(imgArr);
}
function drawName(imgArr) {
 //绘制名字和底部的名字和日期
 canvasPaint.canvas2 = document.getElementById('canvas2');
 canvasPaint.context2 = canvasPaint.canvas2.getContext('2d');
 canvasPaint.ratio = canvasPaint.canvas.height / canvasPaint.canvas.width; //计算画布比例
 canvasPaint.context2.drawImage(imgArr[0], 0, 0, 500, 707);//img0是底图原协议
 canvasPaint.context2.save();
 canvasPaint.context2.translate(50, 190);
 canvasPaint.context2.rotate(270 * Math.PI / 180);
 canvasPaint.context2.drawImage(imgArr[1], 80, 50, 33, 33 * canvasPaint.ratio);//画反转后的名字
 canvasPaint.context2.restore();
 canvasPaint.context2.save();
 canvasPaint.context2.translate(67, 723);//下方的字
 canvasPaint.context2.rotate(270 * Math.PI / 180);
 canvasPaint.context2.drawImage(imgArr[1], 80, 50, 33, 33 * canvasPaint.ratio);//画反转后的名字
 canvasPaint.context2.restore();
 canvasPaint.context2.save();
 canvasPaint.context2.translate(400, 625);//下方的字
 canvasPaint.context2.font = "11px 微软雅黑";
 canvasPaint.context2.fillStyle = "#000";
 canvasPaint.context2.textAlign = "center";
 canvasPaint.context2.textBaseline = "middle";
 var time = new Date().toLocaleString().split(' ')[0];
 canvasPaint.context2.fillText(time, 0, 0);
 canvasPaint.context2.restore();
 prevDrawStatement();
}

这里最主要的还是要理解下画布的rotate和translate方法,就可以把文字旋转任意角度和放到任意位置了

长字手写--画布拖动

JS实现移动端在线签协议功能 

上面签字完成后,我们其实已经用了另一个canvas合成了文字和原协议,现在我们要做无限拖动功能,其实也很简单。

在此之前我们需要清空之前的画布

function prevDrawStatement() {
 clearCanvas();//清除画布
 canvasPaint.finish.innerHTML = "提交抄写";
 canvasPaint.pencilBtn.style.display = 'block';
 canvasPaint.secondState.style.display = 'block';
 canvasPaint.tips.innerHTML = "(最后一步)请抄写屏幕上方引号内的确认语句";
 canvasPaint.tips.style.color = 'red';
 setTimeout(function () {
 canvasPaint.tips.style.color = '#666';
 }, 2000);
 state = STATEMENT;//开始写句子
}

右上角有个移动签字板功能,这里实现的是左右移动,相关代码如下

function togglePencil() {
 if (canvasPaint.canPaint) {
 canvasPaint.canPaint = false;
 canvasPaint.pencilBtn.innerText = "使用签字笔";
 //不能签字时应该把开始写字事件去掉,同时加上document事件
 canvasPaint.canvas.removeEventListener('touchstart', startEventHandler, false);
 document.addEventListener('touchstart', documentStartEventHandler, {passive: false});
 } else {
 canvasPaint.canPaint = true;
 canvasPaint.pencilBtn.innerText = "移动签字板";
 //能签字时应该把开始写字事件绑定上去,同时去掉document事件
 canvasPaint.canvas.addEventListener('touchstart', startEventHandler, {passive: false});
 document.removeEventListener('touchstart', documentStartEventHandler, false);
 }
}
function documentStartEventHandler(event) {
 event.preventDefault();
 canvasPaint.y = event.clientY;
 canvasPaint.top = parseFloat(canvasPaint.canvas.style.top);//画板距离顶部的值
 document.addEventListener('touchmove', documentMoveEventHandler, {passive: false});
 document.addEventListener('touchend', documentEndEventHandler, {passive: false});
}
function documentMoveEventHandler(event) {
 event.preventDefault();
 canvasPaint.newY = event.clientY - canvasPaint.y;
 if (!canvasPaint.canPaint) {
 canvasPaint.canvas.style.top = canvasPaint.newY + canvasPaint.top + 'px';
 if (parseFloat(canvasPaint.canvas.style.top) > 0) {//限制边界
  canvasPaint.canvas.style.top = 0 + 'px';
 }
 }
}

function documentEndEventHandler(event) {
 event.preventDefault();
}

合成长句到协议中并显示最终图片

提交抄写按钮点击后执行下面的函数

function statementDraw(imgArr) {
 canvasPaint.context2.save();
 canvasPaint.context2.translate(52, 690);
 canvasPaint.context2.rotate(270 * Math.PI / 180);
 canvasPaint.context2.drawImage(imgArr[0], 80, 50, 33, 33 * canvasPaint.ratio);//画反转后的名字
 canvasPaint.context2.restore();
 console.log(canvasPaint.canvas2.toDataURL());
 document.getElementById('resultImg').setAttribute('src', canvasPaint.canvas2.toDataURL());
 document.getElementById('resultImg').style.position = 'absolute';
 document.getElementById('resultImg').style.left = 0;
 document.getElementById('resultImg').style.top = 0;
 document.getElementById('resultImg').style.zIndex = 50;
}

在一个风和日丽的下午,刚准备下班,突然接到需求说要做一个在线签协议功能,当时心里想着不就百度一顿拷贝就完事了吗(因为我没用过canvas,所谓初生牛犊不怕虎 ),谁知做起来如此吃力,下面就来记录下历程。

协议模板

分析

如上图,需要做的就是做一个签字板可以在上面写字,写完后点击完成可以生成如上图的图片所示,把签好的字放到指定的位置。

做这个第一反应肯定就是使用canvas绘制路径

我的思路是:

一个字一个字写,每写一个字点一下记录,最后拼接,但想到用户体验问题就pass了这个思路。

最后的思路:一行可以写很多个字,可以让用户滑动canvas,一直写下去(因为协议模板最后还要抄写一段话)

canvas绘制路径--实现签名功能

<canvas id="canvas" style="top:0">您的手机不支持在线签署</canvas>

const canvasPaint = {};//定义一个全局对象,把canvas的各种状态存进去
canvasPaint.canvas = document.getElementById("canvas");
canvasPaint.ctx = document.getElementById("canvas").getContext("2d");
canvasPaint.ctx.lineCap = 'round';//让结束线帽呈现圆滑状
canvasPaint.ctx.lineJoin = 'round';//交汇时呈现圆滑状
canvasPaint.ctx.strokeWidth = 5;//描边宽度
canvasPaint.ctx.lineWidth = 5;//线条宽度

初始化好画布后,我们需要监听画布上的滑动事件

canvasPaint.canvas.addEventListener('touchstart', startEventHandler, {passive: false});
function startEventHandler(event) {
 event.preventDefault();
 canvasPaint.ctx.beginPath();//每次都是一个新路径,不写会和上个字的最后一笔连起来
 canvasPaint.canvas.addEventListener('touchmove', moveEventHandler, {passive: false});
 canvasPaint.canvas.addEventListener('touchend', endEventHandler, {passive: false});
}

passive: false 和 event.preventDefault() 这两个是绝配哦, event.preventDefault() 阻止默认行为,防止在画布上写字时触发了浏览器自带的下拉动作之类的。那 passive: false 是谷歌56版本后提出的新属性,设置为 false 就是告诉浏览器我有阻止默认行为的代码,刚开始不要给我滑动,你需要执行我的 event.preventDefault() 这句代码,如果设置为了 true ,浏览器会自动忽略这句代码,从而不能阻止成功,默认是 true ,所以这里就是坑之一了。

我们继续编写移动划线逻辑

function moveEventHandler(event) {
 event.preventDefault();
 var coverPos = canvasPaint.canvas.getBoundingClientRect();
 canvasPaint.mouseX = event.clientX - coverPos.left;
 canvasPaint.mouseY = event.clientY - coverPos.top;
 if (canvasPaint.canPaint) {//后续为拖动画布功能设置的状态
 canvasPaint.ctx.lineTo(//使用lineTo将移动过的坐标绘制成线
  canvasPaint.mouseX,
  canvasPaint.mouseY
 );
 canvasPaint.ctx.stroke();//绘制
 }
}
function endEventHandler(event) {
 event.preventDefault();
 //抬起手指时取消move和end事件的监听
 canvasPaint.canvas.removeEventListener('touchmove', moveEventHandler, false);
 canvasPaint.canvas.removeEventListener('touchend', endEventHandler, false);
}

canvas--清除屏幕功能

这个功能比较简单就一句话

function clearCanvas() {
 canvasPaint.ctx.clearRect(0, 0, canvasPaint.canvas.width, canvasPaint.canvas.height);
}

提交签名功能

首先需要将画布上的文字转换为img对象,然后使用drawImage绘制到协议上去

preLoadImg(['/assets/index/images/agree.jpg', canvasPaint.canvas.toDataURL()], result);
//agree.jpg为协议名,canvasPaint.canvas.toDataURL()就是签好的字转换为base64的结果
function preLoadImg(source, callBack, args) {
 var pr = [];
 source.forEach(url => {
 var p = loadImage(url)
  .then(function (img) {
  return img;
  })
  .catch(function (err) {
  console.log(err);
  });
 pr.push(p);
 });

 Promise.all(pr)
 .then(function (imgArray) {
  callBack(imgArray, args);
 });

}
function loadImage(url) {
 return new Promise((resolve, reject) => {
 var img = new Image();
 img.onload = function () {
  resolve(img);
 };
 img.onerror = reject;
 img.src = url;
 });
}

由于img赋值src是异步的,我们必须要一个完整的image对象,所以我们使用promise包装,使得我们所有图片都转换完之后再将结果传入回调函数(result)中

function result(imgArr) {
 drawName(imgArr);
}
function drawName(imgArr) {
 //绘制名字和底部的名字和日期
 canvasPaint.canvas2 = document.getElementById('canvas2');
 canvasPaint.context2 = canvasPaint.canvas2.getContext('2d');
 canvasPaint.ratio = canvasPaint.canvas.height / canvasPaint.canvas.width; //计算画布比例
 canvasPaint.context2.drawImage(imgArr[0], 0, 0, 500, 707);//img0是底图原协议
 canvasPaint.context2.save();
 canvasPaint.context2.translate(50, 190);
 canvasPaint.context2.rotate(270 * Math.PI / 180);
 canvasPaint.context2.drawImage(imgArr[1], 80, 50, 33, 33 * canvasPaint.ratio);//画反转后的名字
 canvasPaint.context2.restore();
 canvasPaint.context2.save();
 canvasPaint.context2.translate(67, 723);//下方的字
 canvasPaint.context2.rotate(270 * Math.PI / 180);
 canvasPaint.context2.drawImage(imgArr[1], 80, 50, 33, 33 * canvasPaint.ratio);//画反转后的名字
 canvasPaint.context2.restore();
 canvasPaint.context2.save();
 canvasPaint.context2.translate(400, 625);//下方的字
 canvasPaint.context2.font = "11px 微软雅黑";
 canvasPaint.context2.fillStyle = "#000";
 canvasPaint.context2.textAlign = "center";
 canvasPaint.context2.textBaseline = "middle";
 var time = new Date().toLocaleString().split(' ')[0];
 canvasPaint.context2.fillText(time, 0, 0);
 canvasPaint.context2.restore();
 prevDrawStatement();
}

这里最主要的还是要理解下画布的rotate和translate方法,就可以把文字旋转任意角度和放到任意位置了

长字手写--画布拖动

上面签字完成后,我们其实已经用了另一个canvas合成了文字和原协议,现在我们要做无限拖动功能,其实也很简单。

在此之前我们需要清空之前的画布

function prevDrawStatement() {
 clearCanvas();//清除画布
 canvasPaint.finish.innerHTML = "提交抄写";
 canvasPaint.pencilBtn.style.display = 'block';
 canvasPaint.secondState.style.display = 'block';
 canvasPaint.tips.innerHTML = "(最后一步)请抄写屏幕上方引号内的确认语句";
 canvasPaint.tips.style.color = 'red';
 setTimeout(function () {
 canvasPaint.tips.style.color = '#666';
 }, 2000);
 state = STATEMENT;//开始写句子
}

右上角有个移动签字板功能,这里实现的是左右移动,相关代码如下

function togglePencil() {
 if (canvasPaint.canPaint) {
 canvasPaint.canPaint = false;
 canvasPaint.pencilBtn.innerText = "使用签字笔";
 //不能签字时应该把开始写字事件去掉,同时加上document事件
 canvasPaint.canvas.removeEventListener('touchstart', startEventHandler, false);
 document.addEventListener('touchstart', documentStartEventHandler, {passive: false});
 } else {
 canvasPaint.canPaint = true;
 canvasPaint.pencilBtn.innerText = "移动签字板";
 //能签字时应该把开始写字事件绑定上去,同时去掉document事件
 canvasPaint.canvas.addEventListener('touchstart', startEventHandler, {passive: false});
 document.removeEventListener('touchstart', documentStartEventHandler, false);
 }
}
function documentStartEventHandler(event) {
 event.preventDefault();
 canvasPaint.y = event.clientY;
 canvasPaint.top = parseFloat(canvasPaint.canvas.style.top);//画板距离顶部的值
 document.addEventListener('touchmove', documentMoveEventHandler, {passive: false});
 document.addEventListener('touchend', documentEndEventHandler, {passive: false});
}
function documentMoveEventHandler(event) {
 event.preventDefault();
 canvasPaint.newY = event.clientY - canvasPaint.y;
 if (!canvasPaint.canPaint) {
 canvasPaint.canvas.style.top = canvasPaint.newY + canvasPaint.top + 'px';
 if (parseFloat(canvasPaint.canvas.style.top) > 0) {//限制边界
  canvasPaint.canvas.style.top = 0 + 'px';
 }
 }
}

function documentEndEventHandler(event) {
 event.preventDefault();
}

合成长句到协议中并显示最终图片

提交抄写按钮点击后执行下面的函数

function statementDraw(imgArr) {
 canvasPaint.context2.save();
 canvasPaint.context2.translate(52, 690);
 canvasPaint.context2.rotate(270 * Math.PI / 180);
 canvasPaint.context2.drawImage(imgArr[0], 80, 50, 33, 33 * canvasPaint.ratio);//画反转后的名字
 canvasPaint.context2.restore();
 console.log(canvasPaint.canvas2.toDataURL());
 document.getElementById('resultImg').setAttribute('src', canvasPaint.canvas2.toDataURL());
 document.getElementById('resultImg').style.position = 'absolute';
 document.getElementById('resultImg').style.left = 0;
 document.getElementById('resultImg').style.top = 0;
 document.getElementById('resultImg').style.zIndex = 50;
}

总结

以上所述是小编给大家介绍的JS实现移动端在线签协议功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
jQeury淡入淡出需要注意的问题
Sep 08 Javascript
js Dialog 实践分享
Oct 22 Javascript
代码获取历史上的今天发生的事
Apr 11 Javascript
jquery实现仿新浪微博评论滚动效果
Aug 06 Javascript
javascript发送短信验证码实现代码
Nov 12 Javascript
浅析Node.js实现HTTP文件下载
Aug 05 Javascript
利用js+css+html实现固定table的列头不动
Dec 08 Javascript
bootstrap table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)代码分享
Jan 24 Javascript
使用JS实现图片轮播的实例(前后首尾相接)
Sep 21 Javascript
在iFrame子页面里实现模态框的方法
Aug 17 Javascript
vue监听对象及对象属性问题
Aug 20 Javascript
详解Vue改变数组中对象的属性不重新渲染View的解决方案
Sep 21 Javascript
jQuery表单选择器用法详解
Aug 22 #jQuery
node实现简单的增删改查接口实例代码
Aug 22 #Javascript
微信小程序实现上传图片裁剪图片过程解析
Aug 22 #Javascript
ES6基础之 Promise 对象用法实例详解
Aug 22 #Javascript
ES6基础之数组和对象的拓展实例详解
Aug 22 #Javascript
node express使用HTML模板的方法示例
Aug 22 #Javascript
vue中使用v-model完成组件间的通信
Aug 22 #Javascript
You might like
discuz authcode 经典php加密解密函数解析
2020/07/12 PHP
php设计模式 Facade(外观模式)
2011/06/26 PHP
Zend Framework实现Zend_View集成Smarty模板系统的方法
2016/03/05 PHP
PHP环境搭建的详细步骤
2016/06/30 PHP
jquery解决图片路径不存在执行替换路径
2013/02/06 Javascript
基于jQuery实现文本框缩放以及上下移动功能
2014/11/24 Javascript
JS实现向表格行添加新单元格的方法
2015/03/30 Javascript
日常收集整理的JavaScript常用函数方法
2015/12/10 Javascript
利用js来实现缩略语列表、文献来源链接和快捷键列表
2016/12/16 Javascript
微信小程序实现循环动画效果
2018/07/16 Javascript
JavaScript学习笔记之DOM基础操作实例小结
2019/01/09 Javascript
vue不操作dom实现图片轮播的示例代码
2019/12/18 Javascript
Vue(定时器)解决mounted不能获取到data中的数据问题
2020/07/30 Javascript
Python函数学习笔记
2008/10/07 Python
Python中操作MySQL入门实例
2015/02/08 Python
python爬虫入门教程--正则表达式完全指南(五)
2017/05/25 Python
Python 模拟员工信息数据库操作的实例
2017/10/23 Python
详解Python之unittest单元测试代码
2018/01/24 Python
Python 中Pickle库的使用详解
2018/02/24 Python
Python爬虫小技巧之伪造随机的User-Agent
2018/09/13 Python
Python爬虫常用库的安装及其环境配置
2018/09/19 Python
通过cmd进入python的实例操作
2019/06/26 Python
python opencv调用笔记本摄像头
2019/08/28 Python
Elasticsearch py客户端库安装及使用方法解析
2020/09/14 Python
python中remove函数的踩坑记录
2021/01/04 Python
装上这 14 个插件后,PyCharm 真的是无敌的存在
2021/01/11 Python
html5 application cache遇到的严重问题
2012/12/26 HTML / CSS
HTML5中的拖放实现详解
2017/08/23 HTML / CSS
bonprix荷兰网上商店:便宜的服装、鞋子和家居用品
2020/07/04 全球购物
无工作经验者个人求职信范文
2013/12/22 职场文书
环保建议书400字
2014/05/14 职场文书
小学生纪念九一八事变演讲稿
2014/09/14 职场文书
学校元旦晚会开场白
2015/05/29 职场文书
创业计划书之美容店
2019/09/16 职场文书
MySQL Shell import_table数据导入的实现
2021/08/07 MySQL
Java 常见的限流算法详细分析并实现
2022/04/07 Java/Android