# canvas
# 电子签名
在某些特定的场景中,我们需要在线签字并且上传给后端,这时我们可以通过canvas绘制线来实现
实现思路:
- 1.绘制一个canvas作为签名区域。
- 2.捕获鼠标按下事件
- 3.捕获鼠标移动事件
- 4.捕获鼠标松开事件
- 5.按下鼠标时记录坐标点作为画线开始点
- 6.移动鼠标时实时记录坐标点。
- 7.将点连接起来形成线。
- 8.鼠标松开时停止画线。
实现代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<style>
body {
width: 500px;
}
#canvas {
border: 1px solid;
}
</style>
<body>
<canvas id="canvas" width="500px" height="300px"></canvas>
<input type="color" id="color" />
<select id="size">
<option value="5">粗</option>
<option value="2">细</option>
</select>
<button id="remove">清空</button>
<button id="export">导出</button>
</body>
<script>
// 获取dom元素
let canvas = document.getElementById('canvas')
let ctx = canvas.getContext('2d')
let removeBtn = document.getElementById('remove')
let sizeBtn = document.getElementById('size')
let exportBtn = document.getElementById('export')
let colorBtn = document.getElementById('color')
console.log(canvas, removeBtn, exportBtn, colorBtn)
// 绘画
let drawing = false
ctx.strokeStyle = 'red' // 默认颜色
ctx.lineWidth = 5 // 默认线大小
// 鼠标按下
canvas.addEventListener('mousedown', (e) => {
drawing = true
ctx.beginPath() // 开始绘制
ctx.moveTo(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop) // 绘制起点:当前鼠标坐标 - 画布的默认边距
})
// 鼠标移动
canvas.addEventListener('mousemove', (e) => {
if (drawing) {
ctx.lineTo(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop) // 绘制轨迹:当前鼠标坐标 - 画布的默认边距
ctx.stroke() // 绘制颜色
}
})
// 鼠标松开
canvas.addEventListener('mouseup', (e) => {
drawing = false
ctx.closePath() // 绘制终点
})
// 颜色
colorBtn.addEventListener('change', (e) => {
ctx.strokeStyle = e.target.value
})
// 粗细
sizeBtn.addEventListener('change', (e) => {
ctx.lineWidth = e.target.value
})
// 清空
removeBtn.addEventListener('click', (e) => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
})
// 导出
exportBtn.addEventListener('click', (e) => {
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = 'myImage.png'
a.click()
})
</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# canvas绘制矩形并拖动
实现思路:
- 1.绘制一个canvas作为绘制矩形并拖动区域。
- 2.捕获鼠标点击事件(绘制和拖动都需要点击事件,这里我们需要判断当前操作类型,点击区域有绘制的矩形为拖动反之则绘制矩形)
- 3.捕获鼠标移动事件(实时获取坐标,操作类型为拖动,则改变矩形的坐标位置;操作类型为绘制矩形,则实时绘制矩形)
- 4.捕获鼠标松开事件(同移动事件)
实现代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="./style.css">
</head>
<style>
body{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
canvas {
margin: 50px;
background-color: #b1b3b8;
}
</style>
<body>
<input type="color">
<canvas></canvas>
</body>
<script>
console.log(devicePixelRatio)
// 原始尺寸 = 样式尺寸 * 缩放倍率
const canvas = document.querySelector('canvas') // 画布
const colorPicker = document.querySelector('input') // 颜色选择器
const ctx = canvas.getContext('2d')// 2d绘图环境
function init() {
const w = 500 // 画布宽度
const h = 300 // 画布高度
canvas.width = w * devicePixelRatio
canvas.height = h * devicePixelRatio
canvas.style.width = w + 'px'
canvas.style.height = h + 'px'
}
init()
const shapes = []
class Rectangle {
constructor(color, startX, startY) {
this.color = color
this.startX = startX
this.startY = startY
this.endX = startX
this.endY = startY
}
get minX() {
return Math.min(this.startX, this.endX)
}
get minY() {
return Math.min(this.startY, this.endY)
}
get maxX() {
return Math.max(this.startX, this.endX)
}
get maxY() {
return Math.max(this.startY, this.endY)
}
draw(ctx) {
ctx.beginPath()
ctx.moveTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio)
ctx.lineTo(this.maxX * devicePixelRatio, this.minY * devicePixelRatio)
ctx.lineTo(this.maxX * devicePixelRatio, this.maxY * devicePixelRatio)
ctx.lineTo(this.minX * devicePixelRatio, this.maxY * devicePixelRatio)
ctx.lineTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio)
ctx.fill()
ctx.fillStyle = this.color
ctx.strokeStyle = '#fff'
ctx.lineCap = 'square'
ctx.lineWidth = 2* devicePixelRatio
ctx.stroke()
}
}
canvas.onmousedown= e => {
console.log(e.offsetX, e.offsetY)
const bounding = canvas.getBoundingClientRect()
const rect = new Rectangle(colorPicker.value, e.offsetX, e.offsetY)
const shape = getShape(e.offsetX, e.offsetY)
if(shape){
const { startX, startY,endX,endY} = shape
const mouseX = e.offsetX
const mouseY = e.offsetY
window.onmousemove = e => {
const distX = e.clientX - bounding.left - mouseX
const distY = e.clientY - bounding.top - mouseY
shape.startX = startX + distX
shape.startY = startY + distY
shape.endX = endX + distX
shape.endY = endY + distY
}
console.log('拖动')
} else {
shapes.push(rect)
window.onmousemove = e => {
rect.endX = e.clientX - bounding.left
rect.endY = e.clientY - bounding.top
}
}
window.onmouseup = () => {
window.onmousemove = null
window.onmouseup = null
}
}
function draw() {
requestAnimationFrame(draw)
ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空画布
shapes.forEach(item => {
item.draw(ctx)
})
}
draw()
function getShape(x,y) {
return shapes.find(item => {
return item.minX <= x && item.maxX >= x && item.minY <= y && item.maxY >= y
})
}
</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131