前端實(shí)現(xiàn)電子簽名(web、移動(dòng)端)通用的實(shí)戰(zhàn)過程
前言
在現(xiàn)在的時(shí)代發(fā)展中,從以前的手寫簽名,逐漸衍生出了電子簽名。電子簽名和紙質(zhì)手寫簽名一樣具有法律效應(yīng)。電子簽名目前主要還是在需要個(gè)人確認(rèn)的產(chǎn)品環(huán)節(jié)和司法類相關(guān)的產(chǎn)品上較多。
舉個(gè)常用的例子,大家都用過釘釘,釘釘上面就有電子簽名,相信大家這肯定是知道的。
那作為前端的我們?nèi)绾螌?shí)現(xiàn)電子簽名呢?其實(shí)在html5
中已經(jīng)出現(xiàn)了一個(gè)重要級(jí)別的輔助標(biāo)簽,是啥呢?那就是canvas。
什么是canvas
Canvas(畫布)是在HTML5
中新增的標(biāo)簽用于在網(wǎng)頁實(shí)時(shí)生成圖像,并且可以操作圖像內(nèi)容,基本上它是一個(gè)可以用JavaScript
操作的位圖(bitmap)
。Canvas
對(duì)象表示一個(gè) HTML
畫布元素 -。它沒有自己的行為,但是定義了一個(gè) API 支持腳本化客戶端繪圖操作。
大白話就是canvas
是一個(gè)可以在上面通過javaScript
畫圖的標(biāo)簽,通過其提供的context(上下文)
及Api
進(jìn)行繪制,在這個(gè)過程中canvas
充當(dāng)畫布的角色。
<canvas></canvas>
如何使用
canvas
給我們提供了很多的Api
,供我們使用,我們只需要在body
標(biāo)簽中創(chuàng)建一個(gè)canvas
標(biāo)簽,在script
標(biāo)簽中拿到canvas
這個(gè)標(biāo)簽的節(jié)點(diǎn),并創(chuàng)建context(上下文)
就可以使用了。
... <body> <canvas></canvas> </body> <script> // 獲取canvas 實(shí)例 const canvas = document.querySelector('canvas') canvas.getContext('2d') </script> ...
步入正題。
實(shí)現(xiàn)電子簽名
知道幾何的朋友都很清楚,線有點(diǎn)繪成,面由線繪成。
多點(diǎn)成線,多線成面。
所以我們實(shí)際只需要拿到當(dāng)前觸摸的坐標(biāo)點(diǎn),進(jìn)行成線處理就可以了。
在body中添加canvas標(biāo)簽
在這里我們不僅需要在在body
中添加canvas
標(biāo)簽,我們還需要添加兩個(gè)按鈕,分別是取消
和保存
(后面我們會(huì)用到)。
<body> <canvas></canvas> <div> <button>取消</button> <button>保存</button> </div> </body>
添加文件
我這里全程使用js
進(jìn)行樣式設(shè)置及添加。
// 配置內(nèi)容 const config = { width: 400, // 寬度 height: 200, // 高度 lineWidth: 5, // 線寬 strokeStyle: 'red', // 線條顏色 lineCap: 'round', // 設(shè)置線條兩端圓角 lineJoin: 'round', // 線條交匯處圓角 }
獲取canvas實(shí)例
這里我們使用querySelector
獲取canvas
的dom實(shí)例,并設(shè)置樣式和創(chuàng)建上下文。
// 獲取canvas 實(shí)例 const canvas = document.querySelector('canvas') // 設(shè)置寬高 canvas.width = config.width canvas.height = config.height // 設(shè)置一個(gè)邊框,方便我們查看及使用 canvas.style.border = '1px solid #000' // 創(chuàng)建上下文 const ctx = canvas.getContext('2d')
基礎(chǔ)設(shè)置
我們將canvas
的填充色為透明,并繪制填充一個(gè)矩形,作為我們的畫布,如果不設(shè)置這個(gè)填充背景色,在我們初識(shí)渲染的時(shí)候是一個(gè)黑色背景,這也是它的一個(gè)默認(rèn)色。
// 設(shè)置填充背景色 ctx.fillStyle = 'transparent' // 繪制填充矩形 ctx.fillRect( 0, // x 軸起始繪制位置 0, // y 軸起始繪制位置 config.width, // 寬度 config.height // 高度 );
上次繪制路徑保存
這里我們需要聲明一個(gè)對(duì)象,用來記錄我們上一次繪制的路徑結(jié)束坐標(biāo)點(diǎn)及偏移量。
- 保存上次坐標(biāo)點(diǎn)這個(gè)我不用說大家都懂;
- 為啥需要保存偏移量呢,因?yàn)槭髽?biāo)和畫布上的距離是存在一定的偏移距離,在我們繪制的過程中需要減去這個(gè)偏移量,才是我們實(shí)際的繪制坐標(biāo)。
- 但我發(fā)現(xiàn)
chrome
中不需要減去這個(gè)偏移量,拿到的就是實(shí)際的坐標(biāo),之前在微信小程序中使用就需要減去偏移量,需要在小程序中使用的朋友需要注意這一點(diǎn)哦。
// 保存上次繪制的 坐標(biāo)及偏移量 const client = { offsetX: 0, // 偏移量 offsetY: 0, endX: 0, // 坐標(biāo) endY: 0 }
設(shè)備兼容
我們需要它不僅可以在web
端使用,還需要在移動(dòng)端
使用,我們需要給它做設(shè)備兼容處理。我們通過調(diào)用navigator.userAgent
獲取當(dāng)前設(shè)備信息,進(jìn)行正則匹配判斷。
// 判斷是否為移動(dòng)端 const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent))
初始化
這里我們?cè)诒O(jiān)聽鼠標(biāo)按下(mousedown)
(web端)/觸摸開始(touchstart)
的時(shí)候進(jìn)行初始化,事件監(jiān)聽采用addEventListener
。
// 創(chuàng)建鼠標(biāo)/手勢(shì)按下監(jiān)聽器 window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init)
三元判斷說明: 這里當(dāng)
mobileStatus
為true
時(shí)則表示為移動(dòng)端
,反之則為web端
,后續(xù)使用到的三元
依舊是這個(gè)意思。
聲明初始化方法
我們添加一個(gè)init
方法作為監(jiān)聽鼠標(biāo)按下/觸摸開始的回調(diào)方法。
這里我們需要獲取到當(dāng)前鼠標(biāo)按下/觸摸開始的偏移量和坐標(biāo),進(jìn)行起始點(diǎn)繪制。
Tips:web端可以直接通過
event
中取到,而移動(dòng)端則需要在event.changedTouches[0]
中取到。
這里我們?cè)诔跏蓟笤俦O(jiān)聽鼠標(biāo)的移動(dòng)。
// 初始化 const init = event => { // 獲取偏移量及坐標(biāo) const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event // 修改上次的偏移量及坐標(biāo) client.offsetX = offsetX client.offsetY = offsetY client.endX = pageX client.endY = pageY // 清除以上一次 beginPath 之后的所有路徑,進(jìn)行繪制 ctx.beginPath() // 根據(jù)配置文件設(shè)置進(jìn)行相應(yīng)配置 ctx.lineWidth = config.lineWidth ctx.strokeStyle = config.strokeStyle ctx.lineCap = config.lineCap ctx.lineJoin = config.lineJoin // 設(shè)置畫線起始點(diǎn)位 ctx.moveTo(client.endX, client.endY) // 監(jiān)聽 鼠標(biāo)移動(dòng)或手勢(shì)移動(dòng) window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw) }
繪制
這里我們添加繪制draw
方法,作為監(jiān)聽鼠標(biāo)移動(dòng)/觸摸移動(dòng)的回調(diào)方法。
// 繪制 const draw = event => { // 獲取當(dāng)前坐標(biāo)點(diǎn)位 const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event // 修改最后一次繪制的坐標(biāo)點(diǎn) client.endX = pageX client.endY = pageY // 根據(jù)坐標(biāo)點(diǎn)位移動(dòng)添加線條 ctx.lineTo(pageX , pageY ) // 繪制 ctx.stroke() }
結(jié)束繪制
添加了監(jiān)聽鼠標(biāo)移動(dòng)/觸摸移動(dòng)我們一定要記得取消監(jiān)聽并結(jié)束繪制,不然的話它會(huì)一直監(jiān)聽并繪制的。
這里我們創(chuàng)建一個(gè)cloaseDraw
方法作為鼠標(biāo)彈起/結(jié)束觸摸的回調(diào)方法來結(jié)束繪制并移除鼠標(biāo)移動(dòng)/觸摸移動(dòng)的監(jiān)聽。
canvas
結(jié)束繪制則需要調(diào)用closePath()
讓其結(jié)束繪制
// 結(jié)束繪制 const cloaseDraw = () => { // 結(jié)束繪制 ctx.closePath() // 移除鼠標(biāo)移動(dòng)或手勢(shì)移動(dòng)監(jiān)聽器 window.removeEventListener("mousemove", draw) }
添加結(jié)束回調(diào)監(jiān)聽器
// 創(chuàng)建鼠標(biāo)/手勢(shì) 彈起/離開 監(jiān)聽器 window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw)
ok,現(xiàn)在我們的電子簽名功能還差一丟丟可以實(shí)現(xiàn)完了,現(xiàn)在已經(jīng)可以正常的簽名了。
我們來看一下效果:
取消功能/清空畫布
我們?cè)趧傞_始創(chuàng)建的那兩個(gè)按鈕開始排上用場(chǎng)了。
這里我們創(chuàng)建一個(gè)cancel
的方法作為取消并清空畫布使用
// 取消-清空畫布 const cancel = () => { // 清空當(dāng)前畫布上的所有繪制內(nèi)容 ctx.clearRect(0, 0, config.width, config.height) }
然后我們將這個(gè)方法和取消按鈕
進(jìn)行綁定
<button onclick="cancel()">取消</button>
保存功能
這里我們創(chuàng)建一個(gè)save
的方法作為保存畫布上的內(nèi)容使用。
將畫布上的內(nèi)容保存為圖片/文件的方法有很多,比較常見的是blob
和toDataURL
這兩種方案,但toDataURL
這哥們沒blob
強(qiáng),適配也不咋滴。所以我們這里采用a
標(biāo)簽 ? blob
方案實(shí)現(xiàn)圖片的保存下載。
// 保存-將畫布內(nèi)容保存為圖片 const save = () => { // 將canvas上的內(nèi)容轉(zhuǎn)成blob流 canvas.toBlob(blob => { // 獲取當(dāng)前時(shí)間并轉(zhuǎn)成字符串,用來當(dāng)做文件名 const date = Date.now().toString() // 創(chuàng)建一個(gè) a 標(biāo)簽 const a = document.createElement('a') // 設(shè)置 a 標(biāo)簽的下載文件名 a.download = `${date}.png` // 設(shè)置 a 標(biāo)簽的跳轉(zhuǎn)路徑為 文件流地址 a.href = URL.createObjectURL(blob) // 手動(dòng)觸發(fā) a 標(biāo)簽的點(diǎn)擊事件 a.click() // 移除 a 標(biāo)簽 a.remove() }) }
然后我們將這個(gè)方法和保存按鈕進(jìn)行綁定
<button onclick="save()">保存</button>
我們將剛剛繪制的內(nèi)容進(jìn)行保存,點(diǎn)擊保存按鈕,就會(huì)進(jìn)行下載保存
完整代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> * { margin: 0; padding: 0; } </style> </head> <body> <canvas></canvas> <div> <button onclick="cancel()">取消</button> <button onclick="save()">保存</button> </div> </body> <script> // 配置內(nèi)容 const config = { width: 400, // 寬度 height: 200, // 高度 lineWidth: 5, // 線寬 strokeStyle: 'red', // 線條顏色 lineCap: 'round', // 設(shè)置線條兩端圓角 lineJoin: 'round', // 線條交匯處圓角 } // 獲取canvas 實(shí)例 const canvas = document.querySelector('canvas') // 設(shè)置寬高 canvas.width = config.width canvas.height = config.height // 設(shè)置一個(gè)邊框 canvas.style.border = '1px solid #000' // 創(chuàng)建上下文 const ctx = canvas.getContext('2d') // 設(shè)置填充背景色 ctx.fillStyle = 'transparent' // 繪制填充矩形 ctx.fillRect( 0, // x 軸起始繪制位置 0, // y 軸起始繪制位置 config.width, // 寬度 config.height // 高度 ); // 保存上次繪制的 坐標(biāo)及偏移量 const client = { offsetX: 0, // 偏移量 offsetY: 0, endX: 0, // 坐標(biāo) endY: 0 } // 判斷是否為移動(dòng)端 const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent)) // 初始化 const init = event => { // 獲取偏移量及坐標(biāo) const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event // 修改上次的偏移量及坐標(biāo) client.offsetX = offsetX client.offsetY = offsetY client.endX = pageX client.endY = pageY // 清除以上一次 beginPath 之后的所有路徑,進(jìn)行繪制 ctx.beginPath() // 根據(jù)配置文件設(shè)置相應(yīng)配置 ctx.lineWidth = config.lineWidth ctx.strokeStyle = config.strokeStyle ctx.lineCap = config.lineCap ctx.lineJoin = config.lineJoin // 設(shè)置畫線起始點(diǎn)位 ctx.moveTo(client.endX, client.endY) // 監(jiān)聽 鼠標(biāo)移動(dòng)或手勢(shì)移動(dòng) window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw) } // 繪制 const draw = event => { // 獲取當(dāng)前坐標(biāo)點(diǎn)位 const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event // 修改最后一次繪制的坐標(biāo)點(diǎn) client.endX = pageX client.endY = pageY // 根據(jù)坐標(biāo)點(diǎn)位移動(dòng)添加線條 ctx.lineTo(pageX , pageY ) // 繪制 ctx.stroke() } // 結(jié)束繪制 const cloaseDraw = () => { // 結(jié)束繪制 ctx.closePath() // 移除鼠標(biāo)移動(dòng)或手勢(shì)移動(dòng)監(jiān)聽器 window.removeEventListener("mousemove", draw) } // 創(chuàng)建鼠標(biāo)/手勢(shì)按下監(jiān)聽器 window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init) // 創(chuàng)建鼠標(biāo)/手勢(shì) 彈起/離開 監(jiān)聽器 window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw) // 取消-清空畫布 const cancel = () => { // 清空當(dāng)前畫布上的所有繪制內(nèi)容 ctx.clearRect(0, 0, config.width, config.height) } // 保存-將畫布內(nèi)容保存為圖片 const save = () => { // 將canvas上的內(nèi)容轉(zhuǎn)成blob流 canvas.toBlob(blob => { // 獲取當(dāng)前時(shí)間并轉(zhuǎn)成字符串,用來當(dāng)做文件名 const date = Date.now().toString() // 創(chuàng)建一個(gè) a 標(biāo)簽 const a = document.createElement('a') // 設(shè)置 a 標(biāo)簽的下載文件名 a.download = `${date}.png` // 設(shè)置 a 標(biāo)簽的跳轉(zhuǎn)路徑為 文件流地址 a.href = URL.createObjectURL(blob) // 手動(dòng)觸發(fā) a 標(biāo)簽的點(diǎn)擊事件 a.click() // 移除 a 標(biāo)簽 a.remove() }) } </script> </html>
各內(nèi)核和瀏覽器支持情況
Mozilla 程序從 Gecko 1.8 (Firefox 1.5 (en-US)) 開始支持 <canvas>
。它首先是由 Apple 引入的,用于 OS X Dashboard 和 Safari。Internet Explorer 從 IE9 開始支持<canvas>
,更舊版本的 IE 中,頁面可以通過引入 Google 的 Explorer Canvas 項(xiàng)目中的腳本來獲得<canvas>
支持。Google Chrome 和 Opera 9+ 也支持 <canvas>
。
小程序中提示
在小程序中我們?nèi)绻柩綄?shí)現(xiàn)的話,也是同樣的原理哦,只是我們需要將創(chuàng)建實(shí)例和上下文的Api
進(jìn)行修改,因?yàn)樾〕绦蛑惺菦]有dom
,既然沒有dom
,哪來的操作dom這個(gè)操作呢。
如果是
uni-app
則需要使用uni.createCanvasContext進(jìn)行上下文創(chuàng)建如果是原生微信小程序則使用wx.createCanvasContext進(jìn)行創(chuàng)建(2.9.0)之后的庫不支持
總結(jié)
到此這篇關(guān)于前端實(shí)現(xiàn)電子簽名(web、移動(dòng)端)通用的文章就介紹到這了,更多相關(guān)前端實(shí)現(xiàn)通用電子簽名內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
三劍客:offset、client和scroll還傻傻分不清?
這篇文章主要給大家介紹了三劍客:offset,client和scroll的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12JavaScript實(shí)現(xiàn)購物車圖片局部放大預(yù)覽效果
這篇文章主要為大家詳細(xì)介紹了JavaScript如何通過canvas簡單實(shí)現(xiàn)購物車圖片放大預(yù)覽效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03showModalDialog在谷歌瀏覽器下會(huì)返回Null的解決方法
showModalDialog的返回值在IE、火狐下面都能夠獲取返回值,但是在谷歌瀏覽器下面會(huì)返回Null,下面有個(gè)不錯(cuò)的解決方法,感興趣的朋友可以參考下2013-11-11JavaScript的內(nèi)置對(duì)象Math和字符串詳解
這篇文章主要為大家介紹了JavaScript的內(nèi)置對(duì)象Math和字符串,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-11-11基于JS實(shí)現(xiàn)數(shù)字動(dòng)態(tài)變化顯示效果附源碼
我們經(jīng)??吹揭壕щ娮颖順邮?,數(shù)字動(dòng)態(tài)顯示,動(dòng)態(tài)變化的在指定元素內(nèi)顯示數(shù)字。怎么實(shí)現(xiàn)效果呢?下面小編給大家?guī)砹嘶贘S實(shí)現(xiàn)數(shù)字動(dòng)態(tài)變化顯示效果 ,感興趣的朋友一起看看吧2019-07-07Javascript頁面跳轉(zhuǎn)常見實(shí)現(xiàn)方式匯總
這篇文章主要介紹了Javascript頁面跳轉(zhuǎn)常見實(shí)現(xiàn)方式,結(jié)合實(shí)例匯總分析了JavaScript常用的七種頁面跳轉(zhuǎn)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11cnblogs 代碼高亮顯示后的代碼復(fù)制問題解決實(shí)現(xiàn)代碼
cnblogs是比較有名的技術(shù)博客基地,很多技術(shù)達(dá)人都在里面發(fā)布技術(shù)文章, 不過由于代碼不利于復(fù)制,因?yàn)轫撁胬锩嬗衟re標(biāo)簽等問題2011-12-12JavaScript 跨域之POST實(shí)現(xiàn)方法
本篇文章主要介紹了JavaScript 跨域之POST實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05