這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
在現在的時代發展中,從以前的手寫簽名,逐漸衍生出了電子簽名,電子簽名和紙質手寫簽名一樣具有法律效應,電子簽名目前主要還是在需要個人確認的產品環節和司法類相關的產品上較多,
舉個常用的例子,大家都用過釘釘,釘釘上面就有電子簽名,相信大家這肯定是知道的,
那作為前端的我們如何實作電子簽名呢?其實在html5中已經出現了一個重要級別的輔助標簽,是啥呢?那就是canvas,
什么是canvas
Canvas(畫布)是在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 實體
const canvas = document.querySelector('canvas')
canvas.getContext('2d')
</script>
...
步入正題,
實作電子簽名
知道幾何的朋友都很清楚,線有點繪成,面由線繪成,
多點成線,多線成面,
所以我們實際只需要拿到當前觸摸的坐標點,進行成線處理就可以了,
在body中添加canvas標簽
在這里我們不僅需要在在body中添加canvas標簽,我們還需要添加兩個按鈕,分別是取消和保存(后面我們會用到),
<body>
<canvas></canvas>
<div>
<button>取消</button>
<button>保存</button>
</div>
</body>
添加檔案
我這里全程使用js進行樣式設定及添加,
// 配置內容
const config = {
width: 400, // 寬度
height: 200, // 高度
lineWidth: 5, // 線寬
strokeStyle: 'red', // 線條顏色
lineCap: 'round', // 設定線條兩端圓角
lineJoin: 'round', // 線條交匯處圓角
}
獲取canvas實體
這里我們使用querySelector獲取canvas的dom實體,并設定樣式和創建背景關系,
// 獲取canvas 實體
const canvas = document.querySelector('canvas')
// 設定寬高
canvas.width = config.width
canvas.height = config.height
// 設定一個邊框,方便我們查看及使用
canvas.style.border = '1px solid #000'
// 創建背景關系
const ctx = canvas.getContext('2d')
基礎設定
我們將canvas的填充色為透明,并繪制填充一個矩形,作為我們的畫布,如果不設定這個填充背景色,在我們初識渲染的時候是一個黑色背景,這也是它的一個默認色,
// 設定填充背景色
ctx.fillStyle = 'transparent'
// 繪制填充矩形
ctx.fillRect(
0, // x 軸起始繪制位置
0, // y 軸起始繪制位置
config.width, // 寬度
config.height // 高度
);
上次繪制路徑保存
這里我們需要宣告一個物件,用來記錄我們上一次繪制的路徑結束坐標點及偏移量,
- 保存上次坐標點這個我不用說大家都懂;
- 為啥需要保存偏移量呢,因為滑鼠和畫布上的距離是存在一定的偏移距離,在我們繪制的程序中需要減去這個偏移量,才是我們實際的繪制坐標,
- 但我發現
chrome中不需要減去這個偏移量,拿到的就是實際的坐標,之前在微信小程式中使用就需要減去偏移量,需要在小程式中使用的朋友需要注意這一點哦,
// 保存上次繪制的 坐標及偏移量
const client = {
offsetX: 0, // 偏移量
offsetY: 0,
endX: 0, // 坐標
endY: 0
}
設備兼容
我們需要它不僅可以在web端使用,還需要在移動端使用,我們需要給它做設備兼容處理,我們通過呼叫navigator.userAgent獲取當前設備資訊,進行正則匹配判斷,
// 判斷是否為移動端
const mobileStatus = (/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]中取到,
這里我們在初始化后再監聽滑鼠的移動,
// 初始化
const init = event => {
// 獲取偏移量及坐標
const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
// 修改上次的偏移量及坐標
client.offsetX = offsetX
client.offsetY = offsetY
client.endX = pageX
client.endY = pageY
// 清除以上一次 beginPath 之后的所有路徑,進行繪制
ctx.beginPath()
// 根據組態檔設定進行相應配置
ctx.lineWidth = config.lineWidth
ctx.strokeStyle = config.strokeStyle
ctx.lineCap = config.lineCap
ctx.lineJoin = config.lineJoin
// 設定畫線起始點位
ctx.moveTo(client.endX, client.endY)
// 監聽 滑鼠移動或手勢移動
window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw)
}
繪制
這里我們添加繪制draw方法,作為監聽滑鼠移動/觸摸移動的回呼方法,
// 繪制
const draw = event => {
// 獲取當前坐標點位
const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
// 修改最后一次繪制的坐標點
client.endX = pageX
client.endY = pageY
// 根據坐標點位移動添加線條
ctx.lineTo(pageX , pageY )
// 繪制
ctx.stroke()
}
結束繪制
添加了監聽滑鼠移動/觸摸移動我們一定要記得取消監聽并結束繪制,不然的話它會一直監聽并繪制的,
這里我們創建一個cloaseDraw方法作為滑鼠彈起/結束觸摸的回呼方法來結束繪制并移除滑鼠移動/觸摸移動的監聽,
canvas結束繪制則需要呼叫closePath()讓其結束繪制
// 結束繪制
const cloaseDraw = () => {
// 結束繪制
ctx.closePath()
// 移除滑鼠移動或手勢移動監聽器
window.removeEventListener("mousemove", draw)
}
添加結束回呼監聽器
// 創建滑鼠/手勢 彈起/離開 監聽器
window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw)
ok,現在我們的電子簽名功能還差一丟丟可以實作完了,現在已經可以正常的簽名了,
我們來看一下效果:
取消功能/清慷訓布
我們在剛開始創建的那兩個按鈕開始排上用場了,
這里我們創建一個cancel的方法作為取消并清慷訓布使用
// 取消-清慷訓布
const cancel = () => {
// 清空當前畫布上的所有繪制內容
ctx.clearRect(0, 0, config.width, config.height)
}
然后我們將這個方法和取消按鈕進行系結
<button onclick="cancel()">取消</button>
保存功能
這里我們創建一個save的方法作為保存畫布上的內容使用,
將畫布上的內容保存為圖片/檔案的方法有很多,比較常見的是blob和toDataURL這兩種方案,但toDataURL這哥們沒blob強,適配也不咋滴,所以我們這里采用a標簽 ? blob方案實作圖片的保存下載,
// 保存-將畫布內容保存為圖片
const save = () => {
// 將canvas上的內容轉成blob流
canvas.toBlob(blob => {
// 獲取當前時間并轉成字串,用來當做檔案名
const date = Date.now().toString()
// 創建一個 a 標簽
const a = document.createElement('a')
// 設定 a 標簽的下載檔案名
a.download = `${date}.png`
// 設定 a 標簽的跳轉路徑為 檔案流地址
a.href = https://www.cnblogs.com/smileZAZ/archive/2023/03/15/URL.createObjectURL(blob)
// 手動觸發 a 標簽的點擊事件
a.click()
// 移除 a 標簽
a.remove()
})
}
然后我們將這個方法和保存按鈕進行系結
<button onclick="save()">保存</button>
我們將剛付訓制的內容進行保存,點擊保存按鈕,就會進行下載保存

完整代碼
<!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>
// 配置內容
const config = {
width: 400, // 寬度
height: 200, // 高度
lineWidth: 5, // 線寬
strokeStyle: 'red', // 線條顏色
lineCap: 'round', // 設定線條兩端圓角
lineJoin: 'round', // 線條交匯處圓角
}
// 獲取canvas 實體
const canvas = document.querySelector('canvas')
// 設定寬高
canvas.width = config.width
canvas.height = config.height
// 設定一個邊框
canvas.style.border = '1px solid #000'
// 創建背景關系
const ctx = canvas.getContext('2d')
// 設定填充背景色
ctx.fillStyle = 'transparent'
// 繪制填充矩形
ctx.fillRect(
0, // x 軸起始繪制位置
0, // y 軸起始繪制位置
config.width, // 寬度
config.height // 高度
);
// 保存上次繪制的 坐標及偏移量
const client = {
offsetX: 0, // 偏移量
offsetY: 0,
endX: 0, // 坐標
endY: 0
}
// 判斷是否為移動端
const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent))
// 初始化
const init = event => {
// 獲取偏移量及坐標
const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
// 修改上次的偏移量及坐標
client.offsetX = offsetX
client.offsetY = offsetY
client.endX = pageX
client.endY = pageY
// 清除以上一次 beginPath 之后的所有路徑,進行繪制
ctx.beginPath()
// 根據組態檔設定相應配置
ctx.lineWidth = config.lineWidth
ctx.strokeStyle = config.strokeStyle
ctx.lineCap = config.lineCap
ctx.lineJoin = config.lineJoin
// 設定畫線起始點位
ctx.moveTo(client.endX, client.endY)
// 監聽 滑鼠移動或手勢移動
window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw)
}
// 繪制
const draw = event => {
// 獲取當前坐標點位
const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
// 修改最后一次繪制的坐標點
client.endX = pageX
client.endY = pageY
// 根據坐標點位移動添加線條
ctx.lineTo(pageX , pageY )
// 繪制
ctx.stroke()
}
// 結束繪制
const cloaseDraw = () => {
// 結束繪制
ctx.closePath()
// 移除滑鼠移動或手勢移動監聽器
window.removeEventListener("mousemove", draw)
}
// 創建滑鼠/手勢按下監聽器
window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init)
// 創建滑鼠/手勢 彈起/離開 監聽器
window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw)
// 取消-清慷訓布
const cancel = () => {
// 清空當前畫布上的所有繪制內容
ctx.clearRect(0, 0, config.width, config.height)
}
// 保存-將畫布內容保存為圖片
const save = () => {
// 將canvas上的內容轉成blob流
canvas.toBlob(blob => {
// 獲取當前時間并轉成字串,用來當做檔案名
const date = Date.now().toString()
// 創建一個 a 標簽
const a = document.createElement('a')
// 設定 a 標簽的下載檔案名
a.download = `${date}.png`
// 設定 a 標簽的跳轉路徑為 檔案流地址
a.href = https://www.cnblogs.com/smileZAZ/archive/2023/03/15/URL.createObjectURL(blob)
// 手動觸發 a 標簽的點擊事件
a.click()
// 移除 a 標簽
a.remove()
})
}
</script>

