前言
在现在的时代发展中,从以前的手写签名,逐渐衍生出了电子签名。电子签名和纸质手写签名一样具有法律效应。电子签名目前主要还是在需要个人确认的产品环节和司法类相关的产品上较多。
举个常用的例子,大家都用过钉钉,钉钉上面就有电子签名,相信大家这肯定是知道的。
那作为前端的我们如何实现电子签名呢?其实在html5
中已经出现了一个重要级别的辅助标签,是啥呢?那就是canvas[2]。
什么是canvas
`Canvas(画布)`[3]是在HTML5
中新增的标签用于在网页实时生成图像,并且可以操作图像内容,基本上它是一个可以用JavaScript
操作的位图(bitmap)
。Canvas
对象表示一个HTML
画布元素 -。它没有自己的行为,但是定义了一个 API 支持脚本化客户端绘图操作。
大白话就是canvas
是一个可以在上面通过javaScript
画图的标签,通过其提供的context(上下文)
及Api
进行绘制,在这个过程中canvas
充当画布的角色。
<canvas></canvas>复制代码
如何使用
canvas
给我们提供了很多的Api
,供我们使用,我们只需要在body
标签中创建一个canvas
标签,在script
标签中拿到canvas
这个标签的节点,并创建context(上下文)
就可以使用了。
...<body><canvas></canvas></body><script>//获取canvas实例constcanvas=document.querySelector('canvas')canvas.getContext('2d')</script>...复制代码
步入正题。
实现电子签名
知道几何的朋友都很清楚,线有点绘成,面由线绘成。
多点成线,多线成面。
所以我们实际只需要拿到当前触摸的坐标点,进行成线处理就可以了。
在body
中添加canvas
标签
在这里我们不仅需要在在body
中添加canvas
标签,我们还需要添加两个按钮,分别是取消
和保存
(后面我们会用到)。
<body><canvas></canvas><div><button>取消</button><button>保存</button></div></body>复制代码
添加文件
我这里全程使用js
进行样式设置及添加。
//配置内容constconfig={width:400,//宽度height:200,//高度lineWidth:5,//线宽strokeStyle:'red',//线条颜色lineCap:'round',//设置线条两端圆角lineJoin:'round',//线条交汇处圆角}复制代码
获取canvas
实例
这里我们使用querySelector
获取canvas
的dom实例,并设置样式和创建上下文。
//获取canvas实例constcanvas=document.querySelector('canvas')//设置宽高canvas.width=config.widthcanvas.height=config.height//设置一个边框,方便我们查看及使用canvas.style.border='1pxsolid#000'//创建上下文constctx=canvas.getContext('2d')复制代码
基础设置
我们将canvas
的填充色为透明,并绘制填充一个矩形,作为我们的画布,如果不设置这个填充背景色,在我们初识渲染的时候是一个黑色背景,这也是它的一个默认色。
//设置填充背景色ctx.fillStyle='transparent'//绘制填充矩形ctx.fillRect(0,//x轴起始绘制位置0,//y轴起始绘制位置config.width,//宽度config.height//高度);复制代码
上次绘制路径保存
这里我们需要声明一个对象,用来记录我们上一次绘制的路径结束坐标点及偏移量。
保存上次坐标点这个我不用说大家都懂;
为啥需要保存偏移量呢,因为鼠标和画布上的距离是存在一定的偏移距离,在我们绘制的过程中需要减去这个偏移量,才是我们实际的绘制坐标。
但我发现chrome
中不需要减去这个偏移量,拿到的就是实际的坐标,之前在微信小程序中使用就需要减去偏移量,需要在小程序中使用的朋友需要注意这一点哦。
//保存上次绘制的坐标及偏移量constclient={offsetX:0,//偏移量offsetY:0,endX:0,//坐标endY:0}复制代码
设备兼容
我们需要它不仅可以在web
端使用,还需要在移动端
使用,我们需要给它做设备兼容处理。我们通过调用navigator.userAgent
获取当前设备信息,进行正则匹配判断。
//判断是否为移动端constmobileStatus=(/Mobile|Android|iPhone/i.test(navigator.userAgent))复制代码
初始化
这里我们在监听鼠标按下(mousedown)
(web端)/触摸开始(touchstart)
的时候进行初始化,事件监听采用addEventListener
。
//创建鼠标/手势按下监听器window.addEventListener(mobileStatus?"touchstart":"mousedown",init)复制代码
三元判断说明:这里当
mobileStatus
为true
时则表示为移动端
,反之则为web端
,后续使用到的三元
依旧是这个意思。
声明初始化方法
我们添加一个init
方法作为监听鼠标按下
/触摸开始
的回调方法。
这里我们需要获取到当前鼠标按下
/触摸开始
的偏移量和坐标,进行起始点绘制。
Tips:
web端
可以直接通过event
中取到,而移动端则需要在event.changedTouches[0]
中取到。
这里我们在初始化后再监听鼠标的移动。
//初始化constinit=event=>{//获取偏移量及坐标const{offsetX,offsetY,pageX,pageY}=mobileStatus?event.changedTouches[0]:event//修改上次的偏移量及坐标client.offsetX=offsetXclient.offsetY=offsetYclient.endX=pageXclient.endY=pageY//清除以上一次beginPath之后的所有路径,进行绘制ctx.beginPath()//根据配置文件设置进行相应配置ctx.lineWidth=config.lineWidthctx.strokeStyle=config.strokeStylectx.lineCap=config.lineCapctx.lineJoin=config.lineJoin//设置画线起始点位ctx.moveTo(client.endX,client.endY)//监听鼠标移动或手势移动window.addEventListener(mobileStatus?"touchmove":"mousemove",draw)}复制代码
绘制
这里我们添加绘制draw
方法,作为监听鼠标移动
/触摸移动
的回调方法。
//绘制constdraw=event=>{//获取当前坐标点位const{pageX,pageY}=mobileStatus?event.changedTouches[0]:event//修改最后一次绘制的坐标点client.endX=pageXclient.endY=pageY//根据坐标点位移动添加线条ctx.lineTo(pageX,pageY)//绘制ctx.stroke()}复制代码
结束绘制
添加了监听鼠标移动
/触摸移动
我们一定要记得取消监听并结束绘制,不然的话它会一直监听并绘制的。
这里我们创建一个cloaseDraw
方法作为鼠标弹起
/结束触摸
的回调方法来结束绘制并移除鼠标移动
/触摸移动
的监听。
canvas
结束绘制则需要调用closePath()
让其结束绘制
//结束绘制constcloaseDraw=()=>{//结束绘制ctx.closePath()//移除鼠标移动或手势移动监听器window.removeEventListener("mousemove",draw)}复制代码
添加结束回调监听器
//创建鼠标/手势弹起/离开监听器window.addEventListener(mobileStatus?"touchend":"mouseup",cloaseDraw)复制代码
ok,现在我们的电子签名功能还差一丢丢可以实现完了,现在已经可以正常的签名了。
我们来看一下效果:
取消功能/清空画布
我们在刚开始创建的那两个按钮开始排上用场了。
这里我们创建一个cancel
的方法作为取消并清空画布使用
//取消-清空画布constcancel=()=>{//清空当前画布上的所有绘制内容ctx.clearRect(0,0,config.width,config.height)}复制代码
然后我们将这个方法和取消按钮
进行绑定
<buttononclick="cancel()">取消</button>复制代码
保存功能
这里我们创建一个save
的方法作为保存画布上的内容使用。
将画布上的内容保存为图片/文件
的方法有很多,比较常见的是blob
和toDataURL
这两种方案,但toDataURL
这哥们没blob
强,适配也不咋滴。所以我们这里采用a
标签 ➕blob
方案实现图片的保存下载。
//保存-将画布内容保存为图片constsave=()=>{//将canvas上的内容转成blob流canvas.toBlob(blob=>{//获取当前时间并转成字符串,用来当做文件名constdate=Date.now().toString()//创建一个a标签consta=document.createElement('a')//设置a标签的下载文件名a.download=`${date}.png`//设置a标签的跳转路径为文件流地址a.href=URL.createObjectURL(blob)//手动触发a标签的点击事件a.click()//移除a标签a.remove()})}复制代码
然后我们将这个方法和保存按钮
进行绑定
<buttononclick="save()">保存</button>复制代码
我们将刚刚绘制的内容进行保存,点击保存按钮,就会进行下载保存
完整代码
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title><style>*{margin:0;padding:0;}</style></head><body><canvas></canvas><div><buttononclick="cancel()">取消</button><buttononclick="save()">保存</button></div></body><script>//配置内容constconfig={width:400,//宽度height:200,//高度lineWidth:5,//线宽strokeStyle:'red',//线条颜色lineCap:'round',//设置线条两端圆角lineJoin:'round',//线条交汇处圆角}//获取canvas实例constcanvas=document.querySelector('canvas')//设置宽高canvas.width=config.widthcanvas.height=config.height//设置一个边框canvas.style.border='1pxsolid#000'//创建上下文constctx=canvas.getContext('2d')//设置填充背景色ctx.fillStyle='transparent'//绘制填充矩形ctx.fillRect(0,//x轴起始绘制位置0,//y轴起始绘制位置config.width,//宽度config.height//高度);//保存上次绘制的坐标及偏移量constclient={offsetX:0,//偏移量offsetY:0,endX:0,//坐标endY:0}//判断是否为移动端constmobileStatus=(/Mobile|Android|iPhone/i.test(navigator.userAgent))//初始化constinit=event=>{//获取偏移量及坐标const{offsetX,offsetY,pageX,pageY}=mobileStatus?event.changedTouches[0]:event//修改上次的偏移量及坐标client.offsetX=offsetXclient.offsetY=offsetYclient.endX=pageXclient.endY=pageY//清除以上一次beginPath之后的所有路径,进行绘制ctx.beginPath()//根据配置文件设置相应配置ctx.lineWidth=config.lineWidthctx.strokeStyle=config.strokeStylectx.lineCap=config.lineCapctx.lineJoin=config.lineJoin//设置画线起始点位ctx.moveTo(client.endX,client.endY)//监听鼠标移动或手势移动window.addEventListener(mobileStatus?"touchmove":"mousemove",draw)}//绘制constdraw=event=>{//获取当前坐标点位const{pageX,pageY}=mobileStatus?event.changedTouches[0]:event//修改最后一次绘制的坐标点client.endX=pageXclient.endY=pageY//根据坐标点位移动添加线条ctx.lineTo(pageX,pageY)//绘制ctx.stroke()}//结束绘制constcloaseDraw=()=>{//结束绘制ctx.closePath()//移除鼠标移动或手势移动监听器window.removeEventListener("mousemove",draw)}//创建鼠标/手势按下监听器window.addEventListener(mobileStatus?"touchstart":"mousedown",init)//创建鼠标/手势弹起/离开监听器window.addEventListener(mobileStatus?"touchend":"mouseup",cloaseDraw)//取消-清空画布constcancel=()=>{//清空当前画布上的所有绘制内容ctx.clearRect(0,0,config.width,config.height)}//保存-将画布内容保存为图片constsave=()=>{//将canvas上的内容转成blob流canvas.toBlob(blob=>{//获取当前时间并转成字符串,用来当做文件名constdate=Date.now().toString()//创建一个a标签consta=document.createElement('a')//设置a标签的下载文件名a.download=`${date}.png`//设置a标签的跳转路径为文件流地址a.href=URL.createObjectURL(blob)//手动触发a标签的点击事件a.click()//移除a标签a.remove()})}</script></html>复制代码
各内核和浏览器支持情况
Mozilla 程序从 Gecko 1.8 (Firefox 1.5\(en-US\)[4]) 开始支持<canvas>
。它首先是由 Apple 引入的,用于 OS X Dashboard 和 Safari。Internet Explorer 从 IE9 开始支持<canvas>
,更旧版本的 IE 中,页面可以通过引入 Google 的Explorer Canvas[5]项目中的脚本来获得<canvas>
支持。Google Chrome 和 Opera 9+ 也支持<canvas>
。
小程序中提示
在小程序中我们如果需呀实现的话,也是同样的原理哦,只是我们需要将创建实例和上下文
的Api
进行修改,因为小程序中是没有dom
,既然没有dom
,哪来的操作dom
这个操作呢。
如果是uni-app
则需要使用uni.createCanvasContext[6]进行上下文创建
如果是原生微信小程序则使用`wx.createCanvasContext`[7]进行创建(2.9.0)之后的库不支持
关于本文
作者:桃小瑞
/post/7174251833773752350
最后
欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿
回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!
回复「交流」,吹吹水、聊聊技术、吐吐槽!
回复「阅读」,每日刷刷高质量好文!
如果这篇文章对你有帮助,「在看」是最大的支持
》》面试官也在看的算法资料《《
“在看和转发”就是最大的支持