在微信小程序項目中,開發模塊涉及到手寫簽名功能,微信小程序canvas閃亮登場
前言
微信小程序canvas實現簽名功能
核心內容簡介:
(1)簽名實現,開始,移動,結束
(2)重寫
(3)完成
(4)上傳
一、微信小程序canvas實現簽名功能
效果演示:
(1)簽名實現
(2)重寫
(3)完成
完成后將圖片展示在相應的位置
(4)根據業務需求,可以將圖片上傳到后臺,在需要的地方展示
二、上代碼
1.全部演示
wxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!--pages/canvas-test/canvas-test.wxml--> < view class = "handCenter" > < canvas class = "handWriting" disable-scroll = "true" bindtouchstart = "uploadScaleStart" bindtouchmove = "uploadScaleMove" bindtouchend = "uploadScaleEnd" bindtap = "mouseDown" canvas-id = "handWriting" > </ canvas > </ view > < view class = "handBtn" > < button catchtap = "retDraw" class = "delBtn" >重寫</ button > < button catchtap = "subCanvas" class = "subBtn" >完成</ button > </ view > < view class = "preview" > < image wx:if = "{{tmpPath}}" style = "width:100%;height:100%;" src = "{{tmpPath}}" ></ image > </ view > |
js
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
|
const app = getApp() const api = require( '../../utils/request.js' ); //相對路徑 const apiEev = require( '../../config/config' ); Page({ data: { canvasName: 'handWriting' , ctx: '' , canvasWidth: 0, canvasHeight: 0, transparent: 1, // 透明度 selectColor: 'black' , lineColor: '#1A1A1A' , // 顏色 lineSize: 1.5, // 筆記倍數 lineMin: 0.5, // 最小筆畫半徑 lineMax: 4, // 最大筆畫半徑 pressure: 1, // 默認壓力 smoothness: 60, //順滑度,用60的距離來計算速度 currentPoint: {}, currentLine: [], // 當前線條 firstTouch: true , // 第一次觸發 radius: 1, //畫圓的半徑 cutArea: { top: 0, right: 0, bottom: 0, left: 0 }, //裁剪區域 bethelPoint: [], //保存所有線條 生成的貝塞爾點; lastPoint: 0, chirography: [], //筆跡 currentChirography: {}, //當前筆跡 linePrack: [], //劃線軌跡 , 生成線條的實際點 tmpPath: '' }, // 筆跡開始 uploadScaleStart (e) { if (e.type != 'touchstart' ) return false ; let ctx = this .data.ctx; ctx.setFillStyle( this .data.lineColor); // 初始線條設置顏色 ctx.setGlobalAlpha( this .data.transparent); // 設置半透明 let currentPoint = { x: e.touches[0].x, y: e.touches[0].y } let currentLine = this .data.currentLine; currentLine.unshift({ time: new Date().getTime(), dis: 0, x: currentPoint.x, y: currentPoint.y }) this .setData({ currentPoint, // currentLine }) if ( this .data.firstTouch) { this .setData({ cutArea: { top: currentPoint.y, right: currentPoint.x, bottom: currentPoint.y, left: currentPoint.x }, firstTouch: false }) } this .pointToLine(currentLine); }, // 筆跡移動 uploadScaleMove (e) { if (e.type != 'touchmove' ) return false ; if (e.cancelable) { // 判斷默認行為是否已經被禁用 if (!e.defaultPrevented) { e.preventDefault(); } } let point = { x: e.touches[0].x, y: e.touches[0].y } //測試裁剪 if (point.y < this .data.cutArea.top) { this .data.cutArea.top = point.y; } if (point.y < 0) this .data.cutArea.top = 0; if (point.x > this .data.cutArea.right) { this .data.cutArea.right = point.x; } if ( this .data.canvasWidth - point.x <= 0) { this .data.cutArea.right = this .data.canvasWidth; } if (point.y > this .data.cutArea.bottom) { this .data.cutArea.bottom = point.y; } if ( this .data.canvasHeight - point.y <= 0) { this .data.cutArea.bottom = this .data.canvasHeight; } if (point.x < this .data.cutArea.left) { this .data.cutArea.left = point.x; } if (point.x < 0) this .data.cutArea.left = 0; this .setData({ lastPoint: this .data.currentPoint, currentPoint: point }) let currentLine = this .data.currentLine currentLine.unshift({ time: new Date().getTime(), dis: this .distance( this .data.currentPoint, this .data.lastPoint), x: point.x, y: point.y }) // this.setData({ // currentLine // }) this .pointToLine(currentLine); }, // 筆跡結束 uploadScaleEnd (e) { if (e.type != 'touchend' ) return 0; let point = { x: e.changedTouches[0].x, y: e.changedTouches[0].y } this .setData({ lastPoint: this .data.currentPoint, currentPoint: point }) let currentLine = this .data.currentLine currentLine.unshift({ time: new Date().getTime(), dis: this .distance( this .data.currentPoint, this .data.lastPoint), x: point.x, y: point.y }) // this.setData({ // currentLine // }) if (currentLine.length > 2) { var info = (currentLine[0].time - currentLine[currentLine.length - 1].time) / currentLine.length; //$("#info").text(info.toFixed(2)); } //一筆結束,保存筆跡的坐標點,清空,當前筆跡 //增加判斷是否在手寫區域; this .pointToLine(currentLine); var currentChirography = { lineSize: this .data.lineSize, lineColor: this .data.lineColor }; var chirography = this .data.chirography chirography.unshift(currentChirography); this .setData({ chirography }) var linePrack = this .data.linePrack linePrack.unshift( this .data.currentLine); this .setData({ linePrack, currentLine: [] }) }, onLoad () { let canvasName = this .data.canvasName let ctx = wx.createCanvasContext(canvasName) this .setData({ ctx: ctx }) var query = wx.createSelectorQuery(); query.select( '.handCenter' ).boundingClientRect(rect => { this .setData({ canvasWidth: rect.width, canvasHeight: rect.height }) }).exec(); }, subCanvas(){ // 新增我的 let that = this let ctx = this .data.ctx; ctx.draw( true ,setTimeout( function (){ //我的新增定時器及回調 wx.canvasToTempFilePath({ x: 0, y: 0, width: 375, height: 152, canvasId: 'handWriting' , fileType: 'png' , success: function (res) { that.setData({ tmpPath:res.tempFilePath }) console.log(that.data.tmpPath, '看下是個啥玩意' ) that.upImgs(that.data.tmpPath,0) } }, ctx) },1000)) }, // 新增將保存的圖片路徑上傳到文件服務器 upImgs: function (imgurl, index) { console.log(imgurl, '看下路徑是多少' ) var that = this ; wx.uploadFile({ url: apiEev.api + 'xxxx' , //后臺上傳路徑 filePath: imgurl, name: 'file' , header: { 'content-type' : 'multipart/form-data' }, formData: null , success: function (res) { console.log(res) //接口返回網絡路徑 var data = JSON.parse(res.data) console.log(data, '看下data是個啥' ) if (data.code == "success" ) { console.log( '成功' ) } } }) }, retDraw () { this .data.ctx.clearRect(0, 0, 700, 730) this .data.ctx.draw() this .setData({ tmpPath: '' }) }, //畫兩點之間的線條;參數為:line,會繪制最近的開始的兩個點; pointToLine (line) { this .calcBethelLine(line); return ; }, //計算插值的方式; calcBethelLine (line) { if (line.length <= 1) { line[0].r = this .data.radius; return ; } let x0, x1, x2, y0, y1, y2, r0, r1, r2, len, lastRadius, dis = 0, time = 0, curveValue = 0.5; if (line.length <= 2) { x0 = line[1].x y0 = line[1].y x2 = line[1].x + (line[0].x - line[1].x) * curveValue; y2 = line[1].y + (line[0].y - line[1].y) * curveValue; //x2 = line[1].x; //y2 = line[1].y; x1 = x0 + (x2 - x0) * curveValue; y1 = y0 + (y2 - y0) * curveValue;; } else { x0 = line[2].x + (line[1].x - line[2].x) * curveValue; y0 = line[2].y + (line[1].y - line[2].y) * curveValue; x1 = line[1].x; y1 = line[1].y; x2 = x1 + (line[0].x - x1) * curveValue; y2 = y1 + (line[0].y - y1) * curveValue; } //從計算公式看,三個點分別是(x0,y0),(x1,y1),(x2,y2) ;(x1,y1)這個是控制點,控制點不會落在曲線上;實際上,這個點還會手寫獲取的實際點,卻落在曲線上 len = this .distance({ x: x2, y: y2 }, { x: x0, y: y0 }); lastRadius = this .data.radius; for (let n = 0; n < line.length - 1; n++) { dis += line[n].dis; time += line[n].time - line[n + 1].time; if (dis > this .data.smoothness) break ; } this .setData({ radius: Math.min(time / len * this .data.pressure + this .data.lineMin, this .data.lineMax) * this .data.lineSize }); line[0].r = this .data.radius; //計算筆跡半徑; if (line.length <= 2) { r0 = (lastRadius + this .data.radius) / 2; r1 = r0; r2 = r1; //return; } else { r0 = (line[2].r + line[1].r) / 2; r1 = line[1].r; r2 = (line[1].r + line[0].r) / 2; } let n = 5; let point = []; for (let i = 0; i < n; i++) { let t = i / (n - 1); let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2; let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2; let r = lastRadius + ( this .data.radius - lastRadius) / n * i; point.push({ x: x, y: y, r: r }); if (point.length == 3) { let a = this .ctaCalc(point[0].x, point[0].y, point[0].r, point[1].x, point[1].y, point[1].r, point[2].x, point[2].y, point[2].r); a[0].color = this .data.lineColor; // let bethelPoint = this.data.bethelPoint; // console.log(a) // console.log(this.data.bethelPoint) // bethelPoint = bethelPoint.push(a); this .bethelDraw(a, 1); point = [{ x: x, y: y, r: r }]; } } this .setData({ currentLine: line }) }, //求兩點之間距離 distance (a, b) { let x = b.x - a.x; let y = b.y - a.y; return Math.sqrt(x * x + y * y); }, ctaCalc (x0, y0, r0, x1, y1, r1, x2, y2, r2) { let a = [], vx01, vy01, norm, n_x0, n_y0, vx21, vy21, n_x2, n_y2; vx01 = x1 - x0; vy01 = y1 - y0; norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2; vx01 = vx01 / norm * r0; vy01 = vy01 / norm * r0; n_x0 = vy01; n_y0 = -vx01; vx21 = x1 - x2; vy21 = y1 - y2; norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2; vx21 = vx21 / norm * r2; vy21 = vy21 / norm * r2; n_x2 = -vy21; n_y2 = vx21; a.push({ mx: x0 + n_x0, my: y0 + n_y0, color: "#1A1A1A" }); a.push({ c1x: x1 + n_x0, c1y: y1 + n_y0, c2x: x1 + n_x2, c2y: y1 + n_y2, ex: x2 + n_x2, ey: y2 + n_y2 }); a.push({ c1x: x2 + n_x2 - vx21, c1y: y2 + n_y2 - vy21, c2x: x2 - n_x2 - vx21, c2y: y2 - n_y2 - vy21, ex: x2 - n_x2, ey: y2 - n_y2 }); a.push({ c1x: x1 - n_x2, c1y: y1 - n_y2, c2x: x1 - n_x0, c2y: y1 - n_y0, ex: x0 - n_x0, ey: y0 - n_y0 }); a.push({ c1x: x0 - n_x0 - vx01, c1y: y0 - n_y0 - vy01, c2x: x0 + n_x0 - vx01, c2y: y0 + n_y0 - vy01, ex: x0 + n_x0, ey: y0 + n_y0 }); a[0].mx = a[0].mx.toFixed(1); a[0].mx = parseFloat(a[0].mx); a[0].my = a[0].my.toFixed(1); a[0].my = parseFloat(a[0].my); for (let i = 1; i < a.length; i++) { a[i].c1x = a[i].c1x.toFixed(1); a[i].c1x = parseFloat(a[i].c1x); a[i].c1y = a[i].c1y.toFixed(1); a[i].c1y = parseFloat(a[i].c1y); a[i].c2x = a[i].c2x.toFixed(1); a[i].c2x = parseFloat(a[i].c2x); a[i].c2y = a[i].c2y.toFixed(1); a[i].c2y = parseFloat(a[i].c2y); a[i].ex = a[i].ex.toFixed(1); a[i].ex = parseFloat(a[i].ex); a[i].ey = a[i].ey.toFixed(1); a[i].ey = parseFloat(a[i].ey); } return a; }, bethelDraw (point, is_fill, color) { // 新增我的 let that = this let ctx = this .data.ctx; ctx.beginPath(); ctx.moveTo(point[0].mx, point[0].my); if (undefined != color) { ctx.setFillStyle(color); ctx.setStrokeStyle(color); } else { ctx.setFillStyle(point[0].color); ctx.setStrokeStyle(point[0].color); } for (let i = 1; i < point.length; i++) { ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i].ey); } ctx.stroke(); if (undefined != is_fill) { ctx.fill(); //填充圖形 ( 后繪制的圖形會覆蓋前面的圖形, 繪制時注意先后順序 ) } ctx.draw( true ) }, selectColorEvent (event) { console.log(event) var color = event.currentTarget.dataset.colorValue; var colorSelected = event.currentTarget.dataset.color; this .setData({ selectColor: colorSelected, lineColor: color }) } }) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/* pages/canvas-test2/canvas-test2.wxss */ .canvasId { position : absolute ; left : 50% ; top : 0 ; transform: translate( -50% ); z-index : 1 ; border : 2px dashed #ccc ; border-radius: 8px ; margin-bottom : 66px ; } .handCenter { border : 1px solid red ; } .handWriting { width : 100% ; } .preview { width : 375px ; height : 152px ; } |
2.重點部分分析
(1)簽名基本實現,開始,移動,結束
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
|
// 筆跡開始 uploadScaleStart (e) { if (e.type != 'touchstart' ) return false ; let ctx = this .data.ctx; ctx.setFillStyle( this .data.lineColor); // 初始線條設置顏色 ctx.setGlobalAlpha( this .data.transparent); // 設置半透明 let currentPoint = { x: e.touches[0].x, y: e.touches[0].y } let currentLine = this .data.currentLine; currentLine.unshift({ time: new Date().getTime(), dis: 0, x: currentPoint.x, y: currentPoint.y }) this .setData({ currentPoint, // currentLine }) if ( this .data.firstTouch) { this .setData({ cutArea: { top: currentPoint.y, right: currentPoint.x, bottom: currentPoint.y, left: currentPoint.x }, firstTouch: false }) } this .pointToLine(currentLine); }, // 筆跡移動 uploadScaleMove (e) { if (e.type != 'touchmove' ) return false ; if (e.cancelable) { // 判斷默認行為是否已經被禁用 if (!e.defaultPrevented) { e.preventDefault(); } } let point = { x: e.touches[0].x, y: e.touches[0].y } //測試裁剪 if (point.y < this .data.cutArea.top) { this .data.cutArea.top = point.y; } if (point.y < 0) this .data.cutArea.top = 0; if (point.x > this .data.cutArea.right) { this .data.cutArea.right = point.x; } if ( this .data.canvasWidth - point.x <= 0) { this .data.cutArea.right = this .data.canvasWidth; } if (point.y > this .data.cutArea.bottom) { this .data.cutArea.bottom = point.y; } if ( this .data.canvasHeight - point.y <= 0) { this .data.cutArea.bottom = this .data.canvasHeight; } if (point.x < this .data.cutArea.left) { this .data.cutArea.left = point.x; } if (point.x < 0) this .data.cutArea.left = 0; this .setData({ lastPoint: this .data.currentPoint, currentPoint: point }) let currentLine = this .data.currentLine currentLine.unshift({ time: new Date().getTime(), dis: this .distance( this .data.currentPoint, this .data.lastPoint), x: point.x, y: point.y }) // this.setData({ // currentLine // }) this .pointToLine(currentLine); }, // 筆跡結束 uploadScaleEnd (e) { if (e.type != 'touchend' ) return 0; let point = { x: e.changedTouches[0].x, y: e.changedTouches[0].y } this .setData({ lastPoint: this .data.currentPoint, currentPoint: point }) let currentLine = this .data.currentLine currentLine.unshift({ time: new Date().getTime(), dis: this .distance( this .data.currentPoint, this .data.lastPoint), x: point.x, y: point.y }) // this.setData({ // currentLine // }) if (currentLine.length > 2) { var info = (currentLine[0].time - currentLine[currentLine.length - 1].time) / currentLine.length; //$("#info").text(info.toFixed(2)); } //一筆結束,保存筆跡的坐標點,清空,當前筆跡 //增加判斷是否在手寫區域; this .pointToLine(currentLine); var currentChirography = { lineSize: this .data.lineSize, lineColor: this .data.lineColor }; var chirography = this .data.chirography chirography.unshift(currentChirography); this .setData({ chirography }) var linePrack = this .data.linePrack linePrack.unshift( this .data.currentLine); this .setData({ linePrack, currentLine: [] }) }, |
記得要先在onload中初始化
代碼拿走直接用
(2)重新簽署
大白話就是清空畫布
1
2
3
4
5
6
7
|
retDraw () { this .data.ctx.clearRect(0, 0, 700, 730) this .data.ctx.draw() this .setData({ tmpPath: '' }) }, |
(3)簽署完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
subCanvas(){ // 新增我的 let that = this let ctx = this .data.ctx; ctx.draw( true ,setTimeout( function (){ //我的新增定時器及回調 wx.canvasToTempFilePath({ x: 0, y: 0, width: 375, height: 152, canvasId: 'handWriting' , fileType: 'png' , success: function (res) { that.setData({ tmpPath:res.tempFilePath }) console.log(that.data.tmpPath, '看下是個啥玩意' ) that.upImgs(that.data.tmpPath,0) } }, ctx) },1000)) }, |
里邊的回調比較重要哦:
防止拿不到畫布內容,可以設置延遲;
wx.canvasToTempFilePath方法獲取到畫布圖片內容;
(4)根據業務需求,可以將圖片上傳到后臺,在需要的地方展示
重點是如何上傳到后臺
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// 新增將保存的圖片路徑上傳到文件服務器 upImgs: function (imgurl, index) { console.log(imgurl, '看下路徑是多少' ) var that = this ; wx.uploadFile({ url: apiEev.api + 'xxxx' , //后臺文件上傳的路徑接口 filePath: imgurl, name: 'file' , header: { 'content-type' : 'multipart/form-data' }, formData: null , success: function (res) { console.log(res) //接口返回網絡路徑 var data = JSON.parse(res.data) console.log(data, '看下data是個啥' ) if (data.code == "success" ) { console.log( '成功' ) } } }) }, |
總結
微信小程序canvas實現簽名功能。
特別提醒:在真機調試和體驗版中可能會出現卡頓情況,有條件要發布至預發布中查看是否影響性能。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/weixin_45439379/article/details/112789570