cbvirtua 1.0.42 → 1.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Signature.vue +366 -0
- package/canvas-event-system/build/canvas-event-system/EventSimulator.d.ts +20 -0
- package/canvas-event-system/build/canvas-event-system/EventSimulator.js +51 -0
- package/canvas-event-system/build/canvas-event-system/helpers.d.ts +3 -0
- package/canvas-event-system/build/canvas-event-system/helpers.js +21 -0
- package/canvas-event-system/build/canvas-event-system/index.d.ts +20 -0
- package/canvas-event-system/build/canvas-event-system/index.js +51 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Base.d.ts +12 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Base.js +26 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Circle.d.ts +15 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Circle.js +34 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Path.d.ts +0 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Path.js +0 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Rect.d.ts +16 -0
- package/canvas-event-system/build/canvas-event-system/shapes/Rect.js +33 -0
- package/canvas-event-system/build/canvas-event-system/shapes/index.d.ts +4 -0
- package/canvas-event-system/build/canvas-event-system/shapes/index.js +4 -0
- package/canvas-event-system/build/canvas-event-system/shapes/types.d.ts +19 -0
- package/canvas-event-system/build/canvas-event-system/shapes/types.js +9 -0
- package/canvas-event-system/build/index.d.ts +1 -0
- package/canvas-event-system/build/index.js +24 -0
- package/canvas-event-system/tsconfig.json +1 -1
- package/package.json +1 -1
package/Signature.vue
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="canvasBox">
|
|
3
|
+
<div class="canvasWrap">
|
|
4
|
+
<canvas id="canvas2"></canvas>
|
|
5
|
+
<canvas id="canvas"></canvas>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
import { onMounted } from 'vue'
|
|
12
|
+
import { getStroke } from 'perfect-freehand'
|
|
13
|
+
|
|
14
|
+
onMounted(() => {
|
|
15
|
+
const canvas = document.getElementById('canvas')
|
|
16
|
+
const canvas2 = document.getElementById('canvas2')
|
|
17
|
+
const canvasWidth = 500
|
|
18
|
+
const canvasHeight = 500
|
|
19
|
+
const ratio = Math.max(window.devicePixelRatio, 2)
|
|
20
|
+
|
|
21
|
+
const initCanvas = canvas => {
|
|
22
|
+
canvas.width = canvasWidth * ratio
|
|
23
|
+
canvas.height = canvasHeight * ratio
|
|
24
|
+
canvas.style.width = canvasWidth + 'px'
|
|
25
|
+
canvas.style.height = canvasHeight + 'px'
|
|
26
|
+
const ctx = canvas.getContext('2d')
|
|
27
|
+
ctx.scale(ratio, ratio)
|
|
28
|
+
return ctx
|
|
29
|
+
}
|
|
30
|
+
const ctx = initCanvas(canvas)
|
|
31
|
+
const ctx2 = initCanvas(canvas2)
|
|
32
|
+
|
|
33
|
+
const rect = canvas.getBoundingClientRect()
|
|
34
|
+
const windowToCanvas = e => {
|
|
35
|
+
const x = e.clientX - rect.left
|
|
36
|
+
const y = e.clientY - rect.top
|
|
37
|
+
return {
|
|
38
|
+
x,
|
|
39
|
+
y
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const average = (a, b) => (a + b) / 2
|
|
44
|
+
|
|
45
|
+
const getTwoPointDistance = (x1, y1, x2, y2) => {
|
|
46
|
+
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 增量绘制
|
|
50
|
+
const demo = () => {
|
|
51
|
+
let isMousedown = false
|
|
52
|
+
let lastPos = {
|
|
53
|
+
x: 0,
|
|
54
|
+
y: 0
|
|
55
|
+
}
|
|
56
|
+
canvas.addEventListener('mousedown', e => {
|
|
57
|
+
isMousedown = true
|
|
58
|
+
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
59
|
+
lastPos = windowToCanvas(e)
|
|
60
|
+
})
|
|
61
|
+
window.addEventListener('mousemove', e => {
|
|
62
|
+
if (!isMousedown) return
|
|
63
|
+
ctx.beginPath()
|
|
64
|
+
ctx.moveTo(lastPos.x, lastPos.y)
|
|
65
|
+
lastPos = windowToCanvas(e)
|
|
66
|
+
ctx.lineTo(lastPos.x, lastPos.y)
|
|
67
|
+
ctx.lineWidth = 5
|
|
68
|
+
ctx.stroke()
|
|
69
|
+
})
|
|
70
|
+
window.addEventListener('mouseup', () => {
|
|
71
|
+
isMousedown = false
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
// demo()
|
|
75
|
+
|
|
76
|
+
// 重新绘制
|
|
77
|
+
const demo2 = () => {
|
|
78
|
+
let isMousedown = false
|
|
79
|
+
let pointList = []
|
|
80
|
+
let lastPos = null
|
|
81
|
+
canvas.addEventListener('mousedown', e => {
|
|
82
|
+
isMousedown = true
|
|
83
|
+
lastPos = windowToCanvas(e)
|
|
84
|
+
pointList.push(lastPos)
|
|
85
|
+
})
|
|
86
|
+
window.addEventListener('mousemove', e => {
|
|
87
|
+
if (!isMousedown) return
|
|
88
|
+
const curPos = windowToCanvas(e)
|
|
89
|
+
const distance = getTwoPointDistance(
|
|
90
|
+
curPos.x,
|
|
91
|
+
curPos.y,
|
|
92
|
+
lastPos.x,
|
|
93
|
+
lastPos.y
|
|
94
|
+
)
|
|
95
|
+
if (distance <= 4) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
lastPos = curPos
|
|
99
|
+
pointList.push(curPos)
|
|
100
|
+
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
101
|
+
ctx.beginPath()
|
|
102
|
+
pointList.forEach((point, index) => {
|
|
103
|
+
if (index === 0) {
|
|
104
|
+
ctx.moveTo(point.x, point.y)
|
|
105
|
+
} else {
|
|
106
|
+
ctx.lineTo(point.x, point.y)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
ctx.lineWidth = 5
|
|
110
|
+
ctx.stroke()
|
|
111
|
+
})
|
|
112
|
+
window.addEventListener('mouseup', () => {
|
|
113
|
+
isMousedown = false
|
|
114
|
+
pointList = []
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
// demo2()
|
|
118
|
+
|
|
119
|
+
// 曲线连接
|
|
120
|
+
const demo3 = () => {
|
|
121
|
+
let isMousedown = false
|
|
122
|
+
let pointList = []
|
|
123
|
+
let lastPos = null
|
|
124
|
+
canvas.addEventListener('mousedown', e => {
|
|
125
|
+
isMousedown = true
|
|
126
|
+
lastPos = windowToCanvas(e)
|
|
127
|
+
pointList.push(lastPos)
|
|
128
|
+
})
|
|
129
|
+
window.addEventListener('mousemove', e => {
|
|
130
|
+
if (!isMousedown) return
|
|
131
|
+
const curPos = windowToCanvas(e)
|
|
132
|
+
const distance = getTwoPointDistance(
|
|
133
|
+
curPos.x,
|
|
134
|
+
curPos.y,
|
|
135
|
+
lastPos.x,
|
|
136
|
+
lastPos.y
|
|
137
|
+
)
|
|
138
|
+
if (distance <= 4) {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
lastPos = curPos
|
|
142
|
+
pointList.push(windowToCanvas(e))
|
|
143
|
+
const len = pointList.length
|
|
144
|
+
if (len < 4) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
const lastControlPoint = {
|
|
148
|
+
x: 0,
|
|
149
|
+
y: 0
|
|
150
|
+
}
|
|
151
|
+
const lastEndPoint = {
|
|
152
|
+
x: 0,
|
|
153
|
+
y: 0
|
|
154
|
+
}
|
|
155
|
+
let a = pointList[0]
|
|
156
|
+
let b = pointList[1]
|
|
157
|
+
const c = pointList[2]
|
|
158
|
+
|
|
159
|
+
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
160
|
+
ctx.beginPath()
|
|
161
|
+
ctx.moveTo(a.x, a.y)
|
|
162
|
+
|
|
163
|
+
// 记录控制点和终点
|
|
164
|
+
lastControlPoint.x = b.x
|
|
165
|
+
lastControlPoint.y = b.y
|
|
166
|
+
lastEndPoint.x = average(b.x, c.x)
|
|
167
|
+
lastEndPoint.y = average(b.y, c.y)
|
|
168
|
+
|
|
169
|
+
ctx.quadraticCurveTo(
|
|
170
|
+
lastControlPoint.x,
|
|
171
|
+
lastControlPoint.y,
|
|
172
|
+
lastEndPoint.x,
|
|
173
|
+
lastEndPoint.y
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
for (let i = 2, max = len - 1; i < max; i++) {
|
|
177
|
+
a = pointList[i]
|
|
178
|
+
b = pointList[i + 1]
|
|
179
|
+
// 更新控制点和终点
|
|
180
|
+
lastControlPoint.x =
|
|
181
|
+
lastEndPoint.x + (lastEndPoint.x - lastControlPoint.x)
|
|
182
|
+
lastControlPoint.y =
|
|
183
|
+
lastEndPoint.y + (lastEndPoint.y - lastControlPoint.y)
|
|
184
|
+
lastEndPoint.x = average(a.x, b.x)
|
|
185
|
+
lastEndPoint.y = average(a.y, b.y)
|
|
186
|
+
ctx.quadraticCurveTo(
|
|
187
|
+
lastControlPoint.x,
|
|
188
|
+
lastControlPoint.y,
|
|
189
|
+
lastEndPoint.x,
|
|
190
|
+
lastEndPoint.y
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
ctx.lineWidth = 5
|
|
195
|
+
ctx.stroke()
|
|
196
|
+
})
|
|
197
|
+
window.addEventListener('mouseup', () => {
|
|
198
|
+
isMousedown = false
|
|
199
|
+
pointList = []
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
// demo3()
|
|
203
|
+
|
|
204
|
+
const perfect = () => {
|
|
205
|
+
let isMousedown = false
|
|
206
|
+
let pointList = []
|
|
207
|
+
let lineList = []
|
|
208
|
+
let line = null
|
|
209
|
+
canvas.addEventListener('mousedown', e => {
|
|
210
|
+
isMousedown = true
|
|
211
|
+
pointList = []
|
|
212
|
+
pointList.push(windowToCanvas(e))
|
|
213
|
+
})
|
|
214
|
+
window.addEventListener('mousemove', e => {
|
|
215
|
+
if (!isMousedown) return
|
|
216
|
+
pointList.push(windowToCanvas(e))
|
|
217
|
+
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
218
|
+
lineList.forEach(item => {
|
|
219
|
+
ctx.fill(item)
|
|
220
|
+
})
|
|
221
|
+
const points = getStroke(pointList, {
|
|
222
|
+
size: 16,
|
|
223
|
+
thinning: 0.5,
|
|
224
|
+
smoothing: 0.5,
|
|
225
|
+
streamline: 0.5,
|
|
226
|
+
start: {
|
|
227
|
+
cap: true,
|
|
228
|
+
taper: 0,
|
|
229
|
+
easing: t => t
|
|
230
|
+
},
|
|
231
|
+
end: {
|
|
232
|
+
cap: true,
|
|
233
|
+
taper: 0,
|
|
234
|
+
easing: t => t
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
const pathData = getSvgPathFromStroke(points)
|
|
238
|
+
const path = new Path2D(pathData)
|
|
239
|
+
line = path
|
|
240
|
+
ctx.fill(path)
|
|
241
|
+
})
|
|
242
|
+
window.addEventListener('mouseup', () => {
|
|
243
|
+
isMousedown = false
|
|
244
|
+
lineList.push(line)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const getSvgPathFromStroke = points => {
|
|
248
|
+
const len = points.length
|
|
249
|
+
if (len < 4) {
|
|
250
|
+
return ''
|
|
251
|
+
}
|
|
252
|
+
let a = points[0]
|
|
253
|
+
let b = points[1]
|
|
254
|
+
const c = points[2]
|
|
255
|
+
let result = `M${a[0]},${a[1]} Q${b[0]},${b[1]} ${average(
|
|
256
|
+
b[0],
|
|
257
|
+
c[0]
|
|
258
|
+
)},${average(b[1], c[1])} T`
|
|
259
|
+
for (let i = 2, max = len - 1; i < max; i++) {
|
|
260
|
+
a = points[i]
|
|
261
|
+
b = points[i + 1]
|
|
262
|
+
result += `${average(a[0], b[0])},${average(a[1], b[1])} `
|
|
263
|
+
}
|
|
264
|
+
result += 'Z'
|
|
265
|
+
return result
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// perfect()
|
|
269
|
+
|
|
270
|
+
// 两个canvas
|
|
271
|
+
const perfect2 = () => {
|
|
272
|
+
let isMousedown = false
|
|
273
|
+
let pointList = []
|
|
274
|
+
let lineList = []
|
|
275
|
+
let line = null
|
|
276
|
+
const clearCanvas = _ctx => {
|
|
277
|
+
_ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
278
|
+
}
|
|
279
|
+
canvas.addEventListener('mousedown', e => {
|
|
280
|
+
isMousedown = true
|
|
281
|
+
pointList = []
|
|
282
|
+
pointList.push(windowToCanvas(e))
|
|
283
|
+
})
|
|
284
|
+
window.addEventListener('mousemove', e => {
|
|
285
|
+
if (!isMousedown) return
|
|
286
|
+
pointList.push(windowToCanvas(e))
|
|
287
|
+
clearCanvas(ctx)
|
|
288
|
+
const points = getStroke(pointList, {
|
|
289
|
+
size: 16,
|
|
290
|
+
thinning: 0.5,
|
|
291
|
+
smoothing: 0.5,
|
|
292
|
+
streamline: 0.5,
|
|
293
|
+
start: {
|
|
294
|
+
cap: true,
|
|
295
|
+
taper: 0,
|
|
296
|
+
easing: t => t
|
|
297
|
+
},
|
|
298
|
+
end: {
|
|
299
|
+
cap: true,
|
|
300
|
+
taper: 0,
|
|
301
|
+
easing: t => t
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
const pathData = getSvgPathFromStroke(points)
|
|
305
|
+
const path = new Path2D(pathData)
|
|
306
|
+
line = path
|
|
307
|
+
ctx.fill(path)
|
|
308
|
+
})
|
|
309
|
+
window.addEventListener('mouseup', () => {
|
|
310
|
+
isMousedown = false
|
|
311
|
+
lineList.push(line)
|
|
312
|
+
// 绘制完成后就将图形移到canvas2
|
|
313
|
+
clearCanvas(ctx)
|
|
314
|
+
clearCanvas(ctx2)
|
|
315
|
+
lineList.forEach(item => {
|
|
316
|
+
ctx2.fill(item)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const getSvgPathFromStroke = points => {
|
|
321
|
+
const len = points.length
|
|
322
|
+
if (len < 4) {
|
|
323
|
+
return ''
|
|
324
|
+
}
|
|
325
|
+
let a = points[0]
|
|
326
|
+
let b = points[1]
|
|
327
|
+
const c = points[2]
|
|
328
|
+
let result = `M${a[0]},${a[1]} Q${b[0]},${b[1]} ${average(
|
|
329
|
+
b[0],
|
|
330
|
+
c[0]
|
|
331
|
+
)},${average(b[1], c[1])} T`
|
|
332
|
+
for (let i = 2, max = len - 1; i < max; i++) {
|
|
333
|
+
a = points[i]
|
|
334
|
+
b = points[i + 1]
|
|
335
|
+
result += `${average(a[0], b[0])},${average(a[1], b[1])} `
|
|
336
|
+
}
|
|
337
|
+
result += 'Z'
|
|
338
|
+
return result
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
perfect2()
|
|
342
|
+
})
|
|
343
|
+
</script>
|
|
344
|
+
|
|
345
|
+
<style lang="less" scoped>
|
|
346
|
+
.canvasBox {
|
|
347
|
+
width: 100%;
|
|
348
|
+
height: 100%;
|
|
349
|
+
display: flex;
|
|
350
|
+
justify-content: center;
|
|
351
|
+
align-items: center;
|
|
352
|
+
|
|
353
|
+
.canvasWrap {
|
|
354
|
+
position: relative;
|
|
355
|
+
width: 500px;
|
|
356
|
+
height: 500px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
canvas {
|
|
360
|
+
position: absolute;
|
|
361
|
+
left: 0;
|
|
362
|
+
top: 0;
|
|
363
|
+
border: 1px solid;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Listener, EventNames } from './shapes';
|
|
2
|
+
export interface Action {
|
|
3
|
+
type: ActionType;
|
|
4
|
+
id: string;
|
|
5
|
+
}
|
|
6
|
+
export declare enum ActionType {
|
|
7
|
+
Down = "DOWN",
|
|
8
|
+
Up = "Up",
|
|
9
|
+
Move = "MOVE"
|
|
10
|
+
}
|
|
11
|
+
export default class EventSimulator {
|
|
12
|
+
private listenersMap;
|
|
13
|
+
private lastDownId;
|
|
14
|
+
private lastMoveId;
|
|
15
|
+
addAction(action: Action, evt: MouseEvent): void;
|
|
16
|
+
addListeners(id: string, listeners: {
|
|
17
|
+
[eventName: string]: Listener[];
|
|
18
|
+
}): void;
|
|
19
|
+
fire(id: string, eventName: EventNames, evt: MouseEvent): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { EventNames } from './shapes';
|
|
2
|
+
export var ActionType;
|
|
3
|
+
(function (ActionType) {
|
|
4
|
+
ActionType["Down"] = "DOWN";
|
|
5
|
+
ActionType["Up"] = "Up";
|
|
6
|
+
ActionType["Move"] = "MOVE";
|
|
7
|
+
})(ActionType || (ActionType = {}));
|
|
8
|
+
export default class EventSimulator {
|
|
9
|
+
listenersMap = {};
|
|
10
|
+
lastDownId;
|
|
11
|
+
lastMoveId;
|
|
12
|
+
addAction(action, evt) {
|
|
13
|
+
const { type, id } = action;
|
|
14
|
+
// mousemove
|
|
15
|
+
if (type === ActionType.Move) {
|
|
16
|
+
this.fire(id, EventNames.mousemove, evt);
|
|
17
|
+
}
|
|
18
|
+
// mouseover
|
|
19
|
+
// mouseenter
|
|
20
|
+
if (type === ActionType.Move && (!this.lastMoveId || this.lastMoveId !== id)) {
|
|
21
|
+
this.fire(id, EventNames.mouseenter, evt);
|
|
22
|
+
this.fire(this.lastMoveId, EventNames.mouseleave, evt);
|
|
23
|
+
}
|
|
24
|
+
// mousedown
|
|
25
|
+
if (type === ActionType.Down) {
|
|
26
|
+
this.fire(id, EventNames.mousedown, evt);
|
|
27
|
+
}
|
|
28
|
+
// mouseup
|
|
29
|
+
if (type === ActionType.Up) {
|
|
30
|
+
this.fire(id, EventNames.mouseup, evt);
|
|
31
|
+
}
|
|
32
|
+
// click
|
|
33
|
+
if (type === ActionType.Up && this.lastDownId === id) {
|
|
34
|
+
this.fire(id, EventNames.click, evt);
|
|
35
|
+
}
|
|
36
|
+
if (type === ActionType.Move) {
|
|
37
|
+
this.lastMoveId = action.id;
|
|
38
|
+
}
|
|
39
|
+
else if (type === ActionType.Down) {
|
|
40
|
+
this.lastDownId = action.id;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
addListeners(id, listeners) {
|
|
44
|
+
this.listenersMap[id] = listeners;
|
|
45
|
+
}
|
|
46
|
+
fire(id, eventName, evt) {
|
|
47
|
+
if (this.listenersMap[id] && this.listenersMap[id][eventName]) {
|
|
48
|
+
this.listenersMap[id][eventName].forEach((listener) => listener(evt));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function idToRgba(id) {
|
|
2
|
+
return id.split("-");
|
|
3
|
+
}
|
|
4
|
+
export function rgbaToId(rgba) {
|
|
5
|
+
return rgba.join("-");
|
|
6
|
+
}
|
|
7
|
+
const idPool = {};
|
|
8
|
+
export function createId() {
|
|
9
|
+
let id = createOnceId();
|
|
10
|
+
while (idPool[id]) {
|
|
11
|
+
id = createOnceId();
|
|
12
|
+
}
|
|
13
|
+
return id;
|
|
14
|
+
}
|
|
15
|
+
function createOnceId() {
|
|
16
|
+
return Array(3)
|
|
17
|
+
.fill(0)
|
|
18
|
+
.map(() => Math.ceil(Math.random() * 255))
|
|
19
|
+
.concat(255)
|
|
20
|
+
.join("-");
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Shape } from './shapes/types';
|
|
2
|
+
export * from './shapes';
|
|
3
|
+
export declare class Stage {
|
|
4
|
+
private canvas;
|
|
5
|
+
private osCanvas;
|
|
6
|
+
private ctx;
|
|
7
|
+
private osCtx;
|
|
8
|
+
private dpr;
|
|
9
|
+
private shapes;
|
|
10
|
+
private eventSimulator;
|
|
11
|
+
constructor(canvas: HTMLCanvasElement);
|
|
12
|
+
add(shape: Shape): void;
|
|
13
|
+
private handleCreator;
|
|
14
|
+
/**
|
|
15
|
+
* Determine whether the current position is inside a certain shape, if it is, then return its id
|
|
16
|
+
* @param x
|
|
17
|
+
* @param y
|
|
18
|
+
*/
|
|
19
|
+
private hitJudge;
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { rgbaToId } from './helpers';
|
|
2
|
+
import EventSimulator, { ActionType } from './EventSimulator';
|
|
3
|
+
export * from './shapes';
|
|
4
|
+
export class Stage {
|
|
5
|
+
canvas;
|
|
6
|
+
osCanvas;
|
|
7
|
+
ctx;
|
|
8
|
+
osCtx;
|
|
9
|
+
dpr;
|
|
10
|
+
shapes;
|
|
11
|
+
eventSimulator;
|
|
12
|
+
constructor(canvas) {
|
|
13
|
+
const dpr = window.devicePixelRatio;
|
|
14
|
+
canvas.width = parseInt(canvas.style.width) * dpr;
|
|
15
|
+
canvas.height = parseInt(canvas.style.height) * dpr;
|
|
16
|
+
this.canvas = canvas;
|
|
17
|
+
this.osCanvas = new OffscreenCanvas(canvas.width, canvas.height);
|
|
18
|
+
this.ctx = this.canvas.getContext('2d');
|
|
19
|
+
this.osCtx = this.osCanvas.getContext('2d');
|
|
20
|
+
this.ctx.scale(dpr, dpr);
|
|
21
|
+
this.osCtx.scale(dpr, dpr);
|
|
22
|
+
this.dpr = dpr;
|
|
23
|
+
this.canvas.addEventListener('mousedown', this.handleCreator(ActionType.Down));
|
|
24
|
+
this.canvas.addEventListener('mouseup', this.handleCreator(ActionType.Up));
|
|
25
|
+
this.canvas.addEventListener('mousemove', this.handleCreator(ActionType.Move));
|
|
26
|
+
this.shapes = new Set();
|
|
27
|
+
this.eventSimulator = new EventSimulator();
|
|
28
|
+
}
|
|
29
|
+
add(shape) {
|
|
30
|
+
const id = shape.getId();
|
|
31
|
+
this.eventSimulator.addListeners(id, shape.getListeners());
|
|
32
|
+
this.shapes.add(id);
|
|
33
|
+
shape.draw(this.ctx, this.osCtx);
|
|
34
|
+
}
|
|
35
|
+
handleCreator = (type) => (evt) => {
|
|
36
|
+
const x = evt.offsetX;
|
|
37
|
+
const y = evt.offsetY;
|
|
38
|
+
const id = this.hitJudge(x, y);
|
|
39
|
+
this.eventSimulator.addAction({ type, id }, evt);
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Determine whether the current position is inside a certain shape, if it is, then return its id
|
|
43
|
+
* @param x
|
|
44
|
+
* @param y
|
|
45
|
+
*/
|
|
46
|
+
hitJudge(x, y) {
|
|
47
|
+
const rgba = Array.from(this.osCtx.getImageData(x * this.dpr, y * this.dpr, 1, 1).data);
|
|
48
|
+
const id = rgbaToId(rgba);
|
|
49
|
+
return this.shapes.has(id) ? id : undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EventNames, Listener, Shape } from './types';
|
|
2
|
+
export default class Base implements Shape {
|
|
3
|
+
private listeners;
|
|
4
|
+
id: string;
|
|
5
|
+
constructor();
|
|
6
|
+
draw(ctx: CanvasRenderingContext2D, osCtx: OffscreenCanvasRenderingContext2D): void;
|
|
7
|
+
on(eventName: EventNames, listener: Listener): void;
|
|
8
|
+
getListeners(): {
|
|
9
|
+
[name: string]: Listener[];
|
|
10
|
+
};
|
|
11
|
+
getId(): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createId } from '../helpers';
|
|
2
|
+
export default class Base {
|
|
3
|
+
listeners;
|
|
4
|
+
id;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.id = createId();
|
|
7
|
+
this.listeners = {};
|
|
8
|
+
}
|
|
9
|
+
draw(ctx, osCtx) {
|
|
10
|
+
throw new Error('Method not implemented.');
|
|
11
|
+
}
|
|
12
|
+
on(eventName, listener) {
|
|
13
|
+
if (this.listeners[eventName]) {
|
|
14
|
+
this.listeners[eventName].push(listener);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this.listeners[eventName] = [listener];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
getListeners() {
|
|
21
|
+
return this.listeners;
|
|
22
|
+
}
|
|
23
|
+
getId() {
|
|
24
|
+
return this.id;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Base from './Base';
|
|
2
|
+
interface RectProps {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
radius: number;
|
|
6
|
+
strokeWidth?: number;
|
|
7
|
+
strokeColor?: string;
|
|
8
|
+
fillColor?: string;
|
|
9
|
+
}
|
|
10
|
+
export default class Circle extends Base {
|
|
11
|
+
private props;
|
|
12
|
+
constructor(props: RectProps);
|
|
13
|
+
draw(ctx: CanvasRenderingContext2D, osCtx: OffscreenCanvasRenderingContext2D): void;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { idToRgba } from '../helpers';
|
|
2
|
+
import Base from './Base';
|
|
3
|
+
export default class Circle extends Base {
|
|
4
|
+
props;
|
|
5
|
+
constructor(props) {
|
|
6
|
+
super();
|
|
7
|
+
this.props = props;
|
|
8
|
+
this.props.fillColor = this.props.fillColor || '#fff';
|
|
9
|
+
this.props.strokeColor = this.props.strokeColor || '#000';
|
|
10
|
+
this.props.strokeWidth = this.props.strokeWidth || 1;
|
|
11
|
+
}
|
|
12
|
+
draw(ctx, osCtx) {
|
|
13
|
+
const { x, y, radius, strokeColor, strokeWidth, fillColor } = this.props;
|
|
14
|
+
ctx.save();
|
|
15
|
+
ctx.beginPath();
|
|
16
|
+
ctx.fillStyle = fillColor;
|
|
17
|
+
ctx.strokeStyle = strokeColor;
|
|
18
|
+
ctx.lineWidth = strokeWidth;
|
|
19
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
20
|
+
ctx.fill();
|
|
21
|
+
ctx.stroke();
|
|
22
|
+
ctx.restore();
|
|
23
|
+
const [r, g, b, a] = idToRgba(this.id);
|
|
24
|
+
osCtx.save();
|
|
25
|
+
osCtx.beginPath();
|
|
26
|
+
osCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
27
|
+
osCtx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
28
|
+
osCtx.lineWidth = strokeWidth;
|
|
29
|
+
osCtx.arc(x, y, radius, 0, Math.PI * 2);
|
|
30
|
+
osCtx.fill();
|
|
31
|
+
osCtx.stroke();
|
|
32
|
+
osCtx.restore();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Base from './Base';
|
|
2
|
+
interface RectProps {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
strokeWidth?: number;
|
|
8
|
+
strokeColor?: string;
|
|
9
|
+
fillColor?: string;
|
|
10
|
+
}
|
|
11
|
+
export default class Rect extends Base {
|
|
12
|
+
private props;
|
|
13
|
+
constructor(props: RectProps);
|
|
14
|
+
draw(ctx: CanvasRenderingContext2D, osCtx: OffscreenCanvasRenderingContext2D): void;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { idToRgba } from '../helpers';
|
|
2
|
+
import Base from './Base';
|
|
3
|
+
export default class Rect extends Base {
|
|
4
|
+
props;
|
|
5
|
+
constructor(props) {
|
|
6
|
+
super();
|
|
7
|
+
this.props = props;
|
|
8
|
+
this.props.fillColor = this.props.fillColor || '#fff';
|
|
9
|
+
this.props.strokeColor = this.props.strokeColor || '#000';
|
|
10
|
+
this.props.strokeWidth = this.props.strokeWidth || 1;
|
|
11
|
+
}
|
|
12
|
+
draw(ctx, osCtx) {
|
|
13
|
+
const { x, y, width, height, strokeColor, strokeWidth, fillColor } = this.props;
|
|
14
|
+
ctx.save();
|
|
15
|
+
ctx.beginPath();
|
|
16
|
+
ctx.strokeStyle = strokeColor;
|
|
17
|
+
ctx.lineWidth = strokeWidth;
|
|
18
|
+
ctx.fillStyle = fillColor;
|
|
19
|
+
ctx.rect(x, y, width, height);
|
|
20
|
+
ctx.fill();
|
|
21
|
+
ctx.stroke();
|
|
22
|
+
ctx.restore();
|
|
23
|
+
const [r, g, b, a] = idToRgba(this.id);
|
|
24
|
+
osCtx.save();
|
|
25
|
+
osCtx.beginPath();
|
|
26
|
+
osCtx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
27
|
+
osCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
28
|
+
osCtx.rect(x, y, width, height);
|
|
29
|
+
osCtx.fill();
|
|
30
|
+
osCtx.stroke();
|
|
31
|
+
osCtx.restore();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface Shape {
|
|
2
|
+
draw(ctx: CanvasRenderingContext2D, osCtx: OffscreenCanvasRenderingContext2D): void;
|
|
3
|
+
on(name: string, listener: Listener): void;
|
|
4
|
+
getListeners(): {
|
|
5
|
+
[name: string]: Listener[];
|
|
6
|
+
};
|
|
7
|
+
getId(): string;
|
|
8
|
+
}
|
|
9
|
+
export interface Listener {
|
|
10
|
+
(evt: MouseEvent): void;
|
|
11
|
+
}
|
|
12
|
+
export declare enum EventNames {
|
|
13
|
+
click = "click",
|
|
14
|
+
mousedown = "mousedown",
|
|
15
|
+
mousemove = "mousemove",
|
|
16
|
+
mouseup = "mouseup",
|
|
17
|
+
mouseenter = "mouseenter",
|
|
18
|
+
mouseleave = "mouseleave"
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export var EventNames;
|
|
2
|
+
(function (EventNames) {
|
|
3
|
+
EventNames["click"] = "click";
|
|
4
|
+
EventNames["mousedown"] = "mousedown";
|
|
5
|
+
EventNames["mousemove"] = "mousemove";
|
|
6
|
+
EventNames["mouseup"] = "mouseup";
|
|
7
|
+
EventNames["mouseenter"] = "mouseenter";
|
|
8
|
+
EventNames["mouseleave"] = "mouseleave";
|
|
9
|
+
})(EventNames || (EventNames = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Stage, Rect, Circle, EventNames } from './canvas-event-system';
|
|
2
|
+
const canvas = document.querySelector('#canvas');
|
|
3
|
+
const stage = new Stage(canvas);
|
|
4
|
+
const rect = new Rect({
|
|
5
|
+
x: 50,
|
|
6
|
+
y: 50,
|
|
7
|
+
width: 250,
|
|
8
|
+
height: 175,
|
|
9
|
+
fillColor: 'green',
|
|
10
|
+
});
|
|
11
|
+
const circle = new Circle({
|
|
12
|
+
x: 200,
|
|
13
|
+
y: 200,
|
|
14
|
+
radius: 100,
|
|
15
|
+
fillColor: 'red',
|
|
16
|
+
});
|
|
17
|
+
rect.on(EventNames.mousedown, () => console.log('rect mousedown'));
|
|
18
|
+
rect.on(EventNames.mouseup, () => console.log('rect mouseup'));
|
|
19
|
+
rect.on(EventNames.mouseenter, () => console.log('rect mouseenter'));
|
|
20
|
+
rect.on(EventNames.click, () => console.log('rect click'));
|
|
21
|
+
circle.on(EventNames.click, () => console.log('circle click!!'));
|
|
22
|
+
circle.on(EventNames.mouseleave, () => console.log('circle mouseleave!'));
|
|
23
|
+
stage.add(rect);
|
|
24
|
+
stage.add(circle);
|