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 相关文章推荐
使用TextRange获取输入框中光标的位
Oct 14 Javascript
JS HTML5 音乐天气播放器(Ajax获取天气信息)
May 26 Javascript
javascript笛卡尔积算法实现方法
Apr 08 Javascript
javascript中JSON.parse()与eval()解析json的区别
May 19 Javascript
整理关于Bootstrap导航的慕课笔记
Mar 29 Javascript
关于Bootstrap按钮组件消除黄框的方法
May 19 Javascript
js获取一组日期中最近连续的天数
May 25 Javascript
vue 配置多页面应用的示例代码
Oct 22 Javascript
Javascript的this详解
Mar 23 Javascript
jQuery层叠选择器用法实例分析
Jun 28 jQuery
Javascript查看大图功能代码实现
May 07 Javascript
vue element-ui中table合计指定列求和实例
Nov 02 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
Bo-Blog专用的给Windows服务器的IIS Rewrite程序
2007/08/26 PHP
PHP面向对象程序设计之多态性的应用示例
2018/12/19 PHP
PHP实现简单登录界面
2019/10/23 PHP
PHP数组Key强制类型转换实现原理解析
2020/09/01 PHP
Ext.FormPanel 提交和 Ext.Ajax.request 异步提交函数的区别
2009/11/12 Javascript
javascript 简单抽屉效果的实现代码
2010/03/09 Javascript
js获取图片大小的函数代码
2011/09/20 Javascript
根据表格中的某一列进行排序的javascript代码
2013/11/29 Javascript
JS+CSS实现可拖拽的漂亮圆角特效弹出层完整实例
2015/02/13 Javascript
基于PHP和Mysql相结合使用jqGrid读取数据并显示
2015/12/02 Javascript
Sublime Text 3常用插件及安装方法
2015/12/16 Javascript
jQuery formValidator表单验证
2016/01/07 Javascript
JS组件Form表单验证神器BootstrapValidator
2016/01/26 Javascript
JS中微信小程序自定义底部弹出框
2016/12/22 Javascript
angular4中关于表单的校验示例
2017/10/16 Javascript
微信小程序仿朋友圈发布动态功能
2018/07/15 Javascript
详解在不使用ssr的情况下解决Vue单页面SEO问题
2018/11/08 Javascript
使用layui+ajax实现简单的菜单权限管理及排序的方法
2019/09/10 Javascript
如何利用node.js开发一个生成逐帧动画的小工具
2019/12/01 Javascript
JS实现的定时器展示简单秒表、页面弹框及跳转操作完整示例
2020/01/26 Javascript
javascript用defineProperty实现简单的双向绑定方法
2020/04/03 Javascript
Python元组知识点总结
2019/02/18 Python
基于python生成器封装的协程类
2019/03/20 Python
浅谈tensorflow 中tf.concat()的使用
2020/02/07 Python
Python 使用SFTP和FTP实现对服务器的文件下载功能
2020/12/17 Python
用Python制作音乐海报
2021/01/26 Python
特罗佩亚包官方网站:Tropea
2017/01/03 全球购物
美国渔具店:FishUSA
2019/08/07 全球购物
施惠特软件测试面试题以及笔试题
2015/05/13 面试题
综合办公室个人的自我评价
2013/12/22 职场文书
素食餐饮项目创业计划书
2014/02/02 职场文书
机房搬迁方案
2014/05/01 职场文书
2014年幼儿园重阳节活动方案
2014/09/16 职场文书
单位综合评价意见
2015/06/05 职场文书
CSS的class与id常用的命名规则
2021/05/18 HTML / CSS
MySql开发之自动同步表结构
2021/05/28 MySQL