侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

前端  /  管理员 发布于 4年前   432

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

协议模板

 

分析

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

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

我的思路是:

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

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

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;}


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

协议模板

分析

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

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

我的思路是:

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

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

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实现移动端在线签协议功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!


  • 上一条:
    jQuery表单选择器用法详解
    下一条:
    js的新生代垃圾回收知识点总结
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 使用 Alpine.js 排序插件对元素进行排序(0个评论)
    • 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
    • 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
    • 使用mock-server实现模拟接口对接流程步骤(0个评论)
    • vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-03
    • 2018-04
    • 2018-05
    • 2018-06
    • 2018-09
    • 2018-11
    • 2018-12
    • 2019-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-04
    • 2021-05
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客