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 相关文章推荐
jQuery学习笔记之jQuery+CSS3的浏览器兼容性
Jan 19 Javascript
node.js集成百度UE编辑器
Feb 05 Javascript
理解javascript中的with关键字
Feb 15 Javascript
Bootstrap CSS组件之按钮组(btn-group)
Dec 17 Javascript
详解使用Vue.Js结合Jquery Ajax加载数据的两种方式
Jan 10 Javascript
基于Bootstrap框架实现图片切换
Mar 10 Javascript
jQuery插件FusionWidgets实现的AngularGauge图效果示例【附demo源码】
Mar 23 jQuery
解决webpack打包速度慢的解决办法汇总
Jul 06 Javascript
Vue学习笔记进阶篇之多元素及多组件过渡
Jul 19 Javascript
js封装成插件的步骤方法
Sep 11 Javascript
Layui事件监听的实现(表单和数据表格)
Oct 17 Javascript
Quasar Input:type=&quot;number&quot; 去掉上下小箭头 实现加减按钮样式功能
Apr 09 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
php设计模式 FlyWeight (享元模式)
2011/06/26 PHP
joomla组件开发入门教程
2016/05/04 PHP
PHP+Jquery与ajax相结合实现下拉淡出瀑布流效果【无需插件】
2016/05/06 PHP
分享8个Laravel模型时间戳使用技巧小结
2020/02/12 PHP
js 判断浏览器类型 去全角、半角空格 自动关闭当前窗口
2009/04/10 Javascript
js控制iframe的高度/宽度让其自适应内容
2014/04/09 Javascript
BootStrap使用popover插件实现鼠标经过显示并保持显示框
2016/06/23 Javascript
JS仿京东移动端手指拨动切换轮播图效果
2020/04/10 Javascript
Vue-resource实现ajax请求和跨域请求示例
2017/02/23 Javascript
JavaScript中最常见的三个面试题解析
2017/03/04 Javascript
JS实现队列的先进先出功能示例
2017/05/10 Javascript
原生JS实现隐藏显示图片 JS实现点击切换图片效果
2021/01/27 Javascript
EasyUI的DataGrid每行数据添加操作按钮的实现代码
2017/08/22 Javascript
Node解决简单重复问题系列之Excel内容的获取
2018/01/02 Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
2018/01/12 Javascript
微信小程序 如何获取网络状态
2019/07/26 Javascript
如何通过vscode运行调试javascript代码
2020/07/24 Javascript
python paramiko实现ssh远程访问的方法
2013/12/03 Python
python实现XML解析的方法解析
2019/11/16 Python
详解python环境安装selenium和手动下载安装selenium的方法
2020/03/17 Python
什么是Python中的顺序表
2020/06/02 Python
Python操作word文档插入图片和表格的实例演示
2020/10/25 Python
Python自动化测试基础必备知识点总结
2021/02/07 Python
HTML5公共页面提取作为公用代码的方法
2020/06/30 HTML / CSS
美国鲍勃商店:Bob’s Stores
2018/07/22 全球购物
小米旗下精品生活电商平台:小米有品
2018/12/18 全球购物
女士鞋子、包包和服装在线,第一款10美元:ShoeDazzle
2019/07/26 全球购物
Vuori官网:运动服装的终级表现
2021/01/27 全球购物
施工班组长岗位职责
2014/01/05 职场文书
美术兴趣小组活动总结
2014/07/07 职场文书
学生评语集锦
2015/01/04 职场文书
服装店员工管理制度
2015/08/07 职场文书
《索溪峪的野》教学反思
2016/02/19 职场文书
Spring Bean的实例化之属性注入源码剖析过程
2021/06/13 Java/Android
不同品牌、不同型号对讲机如何互相通联
2022/02/18 无线电
蓝牙耳机怎么连接电脑win11? Win11蓝牙耳机连接电脑的技巧
2023/01/09 数码科技