Front-end implementation of electronic signature (web, mobile) common components
foreword
In the current development of the times, from the previous handwritten signatures, electronic signatures are gradually derived. Electronic signatures have the same legal effect as paper handwritten signatures. At present, electronic signatures are mainly used in product links that require personal confirmation and judicial-related products.
To give a common example, everyone has used DingTalk, and there is an electronic signature on DingTalk, I believe everyone must know this.
So how do we implement electronic signatures as the front end? In fact, html5
an important level of auxiliary tags has appeared in , what is it? That is canvas[2].
what iscanvas
Canvas(画布)
[3] is HTML5
a new tag in , which is used to generate images in real time on web pages, and can manipulate image content, basically it is a user- JavaScript
operable 位图(bitmap)
. Canvas
Object representing a HTML
CanvasElement - . It has no behavior of its own, but defines an API to support scripted client-side drawing operations.
The vernacular is canvas
a label that can be drawn on it , and drawn javaScript
through what it provides , and acts as a canvas in the process .context(上下文)
Api
canvas
<canvas></canvas>
how to use
canvas
It provides us with a lot Api
for us to use. We only need to body
create a canvas
label in the label, script
get canvas
the node of this label in the label, and create context(上下文)
it to use.
<body>
<canvas></canvas>
</body>
<script>
// 获取canvas 实例
const canvas = document.querySelector('canvas')
canvas.getContext('2d')
</script>
Get down to business.
Implement electronic signature
Friends who know geometry are very clear that lines are drawn by points, and surfaces are drawn by lines.
Multiple points form a line, and multiple lines form a plane.
So we actually only need to get the coordinate point of the current touch and perform line processing.
body
Add canvas
tags in
Here we not only need to body
add canvas
labels in , we also need to add two buttons, namely 取消
and 保存
(we will use them later).
<body>
<canvas></canvas>
<div>
<button>取消</button>
<button>保存</button>
</div>
</body>
复制代码
add files
I use it here js
for style setting and adding.
// 配置内容
const config = {
width: 400, // 宽度
height: 200, // 高度
lineWidth: 5, // 线宽
strokeStyle: 'red', // 线条颜色
lineCap: 'round', // 设置线条两端圆角
lineJoin: 'round', // 线条交汇处圆角
}
get canvas
instance
Here we use the querySelector
obtained canvas
dom instance, and set the style and create the context.
// 获取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')
Basic Settings
We set canvas
the fill color to be transparent, and draw a filled rectangle as our canvas. If we do not set this fill background color, it will be a black background when we first know about rendering, which is also its default color.
// 设置填充背景色
ctx.fillStyle = 'transparent'
// 绘制填充矩形
ctx.fillRect(
0, // x 轴起始绘制位置
0, // y 轴起始绘制位置
config.width, // 宽度
config.height // 高度
);
Save the last drawn path
Here we need to declare an object to record the end coordinate point and offset of the path we drew last time.
- I don’t need to say that everyone understands saving the last coordinate point;
- Why do we need to save the offset, because there is a certain offset distance between the mouse and the canvas, and we need to subtract this offset in the process of drawing, which is our actual drawing coordinates.
- But I found
chrome
that there is no need to subtract this offset, and what I get is the actual coordinates. Before using it in the WeChat applet, I need to subtract the offset. Friends who need to use it in the applet need to pay attention to this.
// 保存上次绘制的 坐标及偏移量
const client = {
offsetX: 0, // 偏移量
offsetY: 0,
endX: 0, // 坐标
endY: 0
}
device compatible
We need it not only to be used on web
the terminal, but also to 移动端
be used, and we need to make it compatible with devices. We navigator.userAgent
obtain the current device information by calling, and make regular matching judgments.
// 判断是否为移动端
const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent))
复制代码
initialization
Here we initialize when listening 鼠标按下(mousedown)
(web side) / , event monitoring adopts .触摸开始(touchstart)
addEventListener
// 创建鼠标/手势按下监听器
window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init)
Explanation of the ternary judgment: Here, when
mobileStatus
it istrue
, it is expressed as移动端
, and vice versa , it is stillweb端
used in the subsequent use .三元
declare initialization method
We add a init
method as a callback method for listener 鼠标按下
/ 触摸开始
.
Here we need to get the offset and coordinates of the current 鼠标按下
/ to draw the starting point.触摸开始
Tips: It can be obtained
web端
directly through the Internet, while the mobile terminal needs to be obtained in the Internet.event
event.changedTouches[0]
Here we monitor the mouse movement after initialization.
// 初始化
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
Here we add the drawing draw
method as the callback method of listening 鼠标移动
/ 触摸移动
.
// 绘制
const draw = event => {
// 获取当前坐标点位
const {
pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
// 修改最后一次绘制的坐标点
client.endX = pageX
client.endY = pageY
// 根据坐标点位移动添加线条
ctx.lineTo(pageX , pageY )
// 绘制
ctx.stroke()
}
复制代码
end drawing
Added monitoring 鼠标移动
/ 触摸移动
We must remember to cancel the monitoring and end the drawing, otherwise it will always monitor and draw.
Here we create a cloaseDraw
method as the callback method of 鼠标弹起
/ 结束触摸
to end the drawing and remove the listener of 鼠标移动
/ .触摸移动
canvas
To end the drawing, you need to call to closePath()
let it end the drawing
// 结束绘制
const cloaseDraw = () => {
// 结束绘制
ctx.closePath()
// 移除鼠标移动或手势移动监听器
window.removeEventListener("mousemove", draw)
}
复制代码
Add end callback listener
// 创建鼠标/手势 弹起/离开 监听器
window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw)
Ok, now our electronic signature function is almost finished, and now we can sign normally.
Let's take a look at the effect:
Cancel function/clear canvas
The two buttons we created at the beginning come in handy.
Here we create a cancel
method to cancel and clear the canvas
// 取消-清空画布
const cancel = () => {
// 清空当前画布上的所有绘制内容
ctx.clearRect(0, 0, config.width, config.height)
}
Then we 取消按钮
bind this method with
<button onclick="cancel()">取消</button>
save function
Here we create a save
method to save the content on the canvas.
There are many ways to save the content on the canvas , and these two solutions 图片/文件
are more common , but this buddy is not strong, and the adaptation is not good. So here we use the label➕ scheme to save and download pictures.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 = URL.createObjectURL(blob)
// 手动触发 a 标签的点击事件
a.click()
// 移除 a 标签
a.remove()
})
}
Then we 保存按钮
bind this method with
<button onclick="save()">保存</button>
We will save the content we just drew, click the save button, and it will be downloaded and saved
image.png
full code
<!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 = URL.createObjectURL(blob)
// 手动触发 a 标签的点击事件
a.click()
// 移除 a 标签
a.remove()
})
}
</script>
</html>
Kernel and browser support
Mozilla programs support it starting with Gecko 1.8 (Firefox 1.5 (en-US)[4]) <canvas>
. It was first introduced by Apple for OS X Dashboard and Safari. Internet Explorer has been supported since IE9 <canvas>
. In older versions of IE, pages can be supported by introducing scripts from Google's Explorer Canvas[5] project <canvas>
. Google Chrome and Opera 9+ are also supported <canvas>
.
Prompt in the applet
If we need to implement it in the small program, it is the same principle, but we need to 创建实例和上下文
modify Api
it, because there is no such operation in the small program, dom
since there is no such operation dom
, where does 操作dom
this operation come from?
- If yes
uni-app
, you need to use uni.createCanvasContext[6] for context creation