@douyinfe/semi-foundation 2.72.2 → 2.73.0-beta.0

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.
Files changed (60) hide show
  1. package/audioPlayer/audioPlayer.scss +217 -0
  2. package/audioPlayer/constants.ts +7 -0
  3. package/audioPlayer/foundation.ts +103 -0
  4. package/audioPlayer/variables.scss +55 -0
  5. package/button/iconButton.scss +8 -0
  6. package/cropper/constants.ts +26 -0
  7. package/cropper/cropper.scss +116 -0
  8. package/cropper/foundation.ts +821 -0
  9. package/cropper/utils.ts +12 -0
  10. package/cropper/variables.scss +6 -0
  11. package/dragMove/foundation.ts +12 -0
  12. package/jsonViewer/foundation.ts +6 -0
  13. package/jsonViewer/jsonViewer.scss +8 -3
  14. package/lib/cjs/audioPlayer/audioPlayer.css +188 -0
  15. package/lib/cjs/audioPlayer/audioPlayer.scss +217 -0
  16. package/lib/cjs/audioPlayer/constants.d.ts +4 -0
  17. package/lib/cjs/audioPlayer/constants.js +10 -0
  18. package/lib/cjs/audioPlayer/foundation.d.ts +41 -0
  19. package/lib/cjs/audioPlayer/foundation.js +79 -0
  20. package/lib/cjs/audioPlayer/variables.scss +55 -0
  21. package/lib/cjs/button/iconButton.css +8 -0
  22. package/lib/cjs/button/iconButton.scss +8 -0
  23. package/lib/cjs/cropper/constants.d.ts +17 -0
  24. package/lib/cjs/cropper/constants.js +24 -0
  25. package/lib/cjs/cropper/cropper.css +97 -0
  26. package/lib/cjs/cropper/cropper.scss +116 -0
  27. package/lib/cjs/cropper/foundation.d.ts +101 -0
  28. package/lib/cjs/cropper/foundation.js +786 -0
  29. package/lib/cjs/cropper/utils.d.ts +2 -0
  30. package/lib/cjs/cropper/utils.js +19 -0
  31. package/lib/cjs/cropper/variables.scss +6 -0
  32. package/lib/cjs/dragMove/foundation.d.ts +2 -0
  33. package/lib/cjs/dragMove/foundation.js +10 -0
  34. package/lib/cjs/jsonViewer/foundation.js +6 -0
  35. package/lib/cjs/jsonViewer/jsonViewer.css +8 -2
  36. package/lib/cjs/jsonViewer/jsonViewer.scss +8 -3
  37. package/lib/es/audioPlayer/audioPlayer.css +188 -0
  38. package/lib/es/audioPlayer/audioPlayer.scss +217 -0
  39. package/lib/es/audioPlayer/constants.d.ts +4 -0
  40. package/lib/es/audioPlayer/constants.js +5 -0
  41. package/lib/es/audioPlayer/foundation.d.ts +41 -0
  42. package/lib/es/audioPlayer/foundation.js +72 -0
  43. package/lib/es/audioPlayer/variables.scss +55 -0
  44. package/lib/es/button/iconButton.css +8 -0
  45. package/lib/es/button/iconButton.scss +8 -0
  46. package/lib/es/cropper/constants.d.ts +17 -0
  47. package/lib/es/cropper/constants.js +19 -0
  48. package/lib/es/cropper/cropper.css +97 -0
  49. package/lib/es/cropper/cropper.scss +116 -0
  50. package/lib/es/cropper/foundation.d.ts +101 -0
  51. package/lib/es/cropper/foundation.js +778 -0
  52. package/lib/es/cropper/utils.d.ts +2 -0
  53. package/lib/es/cropper/utils.js +12 -0
  54. package/lib/es/cropper/variables.scss +6 -0
  55. package/lib/es/dragMove/foundation.d.ts +2 -0
  56. package/lib/es/dragMove/foundation.js +10 -0
  57. package/lib/es/jsonViewer/foundation.js +6 -0
  58. package/lib/es/jsonViewer/jsonViewer.css +8 -2
  59. package/lib/es/jsonViewer/jsonViewer.scss +8 -3
  60. package/package.json +4 -4
@@ -0,0 +1,821 @@
1
+ import BaseFoundation, { DefaultAdapter } from "../base/foundation";
2
+ import { getMiddle, getAspectHW } from "./utils";
3
+
4
+ export interface CropperAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
5
+ getContainer: () => HTMLElement;
6
+ notifyZoomChange: (zoom: number) => void;
7
+ getImg: () => HTMLImageElement
8
+ }
9
+
10
+ interface Point {
11
+ x: number;
12
+ y: number
13
+ }
14
+
15
+ export interface ImageData {
16
+ originalWidth: number;
17
+ originalHeight: number;
18
+ scale: number
19
+ }
20
+
21
+ export interface ImageDataState {
22
+ width: number;
23
+ height: number;
24
+ centerPoint: Point
25
+ }
26
+
27
+ export interface CropperBox {
28
+ width: number;
29
+ height: number;
30
+ centerPoint: Point
31
+ }
32
+
33
+ export interface ContainerData {
34
+ width: number;
35
+ height: number
36
+ }
37
+
38
+ export interface CropperBoxBorder {
39
+ borderTop: number;
40
+ borderLeft: number
41
+ }
42
+
43
+ export default class CropperFoundation <P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CropperAdapter<P, S>, P, S> {
44
+ imgData: ImageData;
45
+ containerData: ContainerData;
46
+ boxMoveDir: string;
47
+ cropperBoxMoveStart: Point;
48
+ imgMoveStart: Point;
49
+ moveRange: {
50
+ xMax: number;
51
+ xMin: number;
52
+ yMax: number;
53
+ yMin: number
54
+ };
55
+ boxMoveParam: {
56
+ paramX: number;
57
+ paramY: number
58
+ }
59
+ cropperBox: CropperBoxBorder;
60
+ rangeX: [number, number];
61
+ rangeY: [number, number];
62
+ initial: boolean;
63
+
64
+ constructor(adapter: CropperAdapter<P, S>) {
65
+ super({ ...adapter });
66
+
67
+ this.containerData = {} as ContainerData;
68
+ this.imgData = {} as ImageData;
69
+ this.boxMoveDir = '';
70
+ this.boxMoveParam = {
71
+ paramX: 0,
72
+ paramY: 0,
73
+ };
74
+ this.rangeX = null;
75
+ this.rangeY = null;
76
+ this.initial = false;
77
+ }
78
+
79
+ init() {
80
+ // 获取容器的宽高
81
+ // get cropping Container 's width & height
82
+ const container = this._adapter.getContainer();
83
+ this.containerData.width = container.clientWidth;
84
+ this.containerData.height = container.clientHeight;
85
+ this.cropperBoxMoveStart = null;
86
+ }
87
+
88
+ destroy() {
89
+ this.unBindMoveEvent();
90
+ this.unBindResizeEvent();
91
+ }
92
+
93
+ getImgDataWhenResize = (ratio: number) => {
94
+ const { imgData } = this.getStates();
95
+ const newImgData = {
96
+ width: imgData.width * ratio,
97
+ height: imgData.height * ratio,
98
+ centerPoint: {
99
+ x: imgData.centerPoint.x * ratio,
100
+ y: imgData.centerPoint.y * ratio,
101
+ }
102
+ };
103
+ this.imgData.scale *= ratio;
104
+ return newImgData;
105
+ }
106
+
107
+ getCropperBoxWhenResize = (ratio: number, newContainerData: ContainerData) => {
108
+ const { cropperBox } = this.getStates();
109
+ const { aspectRatio } = this.getProps();
110
+ const tempCropperBox = {
111
+ width: cropperBox.width * ratio,
112
+ height: cropperBox.height * ratio,
113
+ centerPoint: {
114
+ x: cropperBox.centerPoint.x * ratio,
115
+ y: cropperBox.centerPoint.y * ratio,
116
+ }
117
+ };
118
+ let xMin = tempCropperBox.centerPoint.x - tempCropperBox.width / 2;
119
+ let xMax = tempCropperBox.centerPoint.x + tempCropperBox.width / 2;
120
+ let yMin = tempCropperBox.centerPoint.y - tempCropperBox.height / 2;
121
+ let yMax = tempCropperBox.centerPoint.y + tempCropperBox.height / 2;
122
+ if (aspectRatio) {
123
+ if (xMax > newContainerData.width) {
124
+ xMax = newContainerData.width;
125
+ xMin = tempCropperBox.width > newContainerData.width ?
126
+ 0 : newContainerData.width - tempCropperBox.width;
127
+ tempCropperBox.width = xMax - xMin;
128
+ tempCropperBox.height = tempCropperBox.width / aspectRatio;
129
+ yMax = yMin + tempCropperBox.height;
130
+ }
131
+ if (yMax > newContainerData.height) {
132
+ yMax = newContainerData.height;
133
+ yMin = tempCropperBox.height > newContainerData.height ?
134
+ 0 : newContainerData.height - tempCropperBox.height;
135
+ tempCropperBox.height = yMax - yMin;
136
+ tempCropperBox.width = tempCropperBox.height * aspectRatio;
137
+ xMax = xMin + tempCropperBox.width;
138
+ }
139
+ } else {
140
+ if (xMax > newContainerData.width) {
141
+ xMax = newContainerData.width;
142
+ xMin = tempCropperBox.width > newContainerData.width ?
143
+ 0 : newContainerData.width - tempCropperBox.width;
144
+ }
145
+ if (yMax > newContainerData.height) {
146
+ yMax = newContainerData.height;
147
+ yMin = tempCropperBox.height > newContainerData.height ?
148
+ 0 : newContainerData.height - tempCropperBox.height;
149
+ }
150
+ }
151
+ return {
152
+ width: xMax - xMin,
153
+ height: yMax - yMin,
154
+ centerPoint: {
155
+ x: (xMax + xMin) / 2,
156
+ y: (yMax + yMin) / 2,
157
+ }
158
+ };
159
+ }
160
+
161
+
162
+ handleResize = () => {
163
+ const { loaded } = this.getStates();
164
+ if (!this.initial) {
165
+ this.initial = true;
166
+ return;
167
+ }
168
+ if (!loaded) {
169
+ return;
170
+ }
171
+ const container = this._adapter.getContainer();
172
+ const newContainerData = {
173
+ width: container.clientWidth,
174
+ height: container.clientHeight,
175
+ };
176
+ const ratio = newContainerData.width / this.containerData.width;
177
+ const newImgData = this.getImgDataWhenResize(ratio);
178
+ const newCropperBox = this.getCropperBoxWhenResize(ratio, newContainerData);
179
+
180
+ this.containerData = newContainerData;
181
+ this.setState({
182
+ imgData: newImgData,
183
+ cropperBox: newCropperBox,
184
+ } as any);
185
+ }
186
+
187
+ handleImageLoad = (e: any) => {
188
+ /**
189
+ * 1. 图片加载完成后,获得图片的原始大小
190
+ * 2. 计算图片的缩放比例,中心点位置
191
+ */
192
+ const { naturalWidth, naturalHeight } = e.target;
193
+ const { width: containerWidth, height: containerHeight } = this.containerData;
194
+ this.imgData.originalWidth = naturalWidth;
195
+ this.imgData.originalHeight = naturalHeight;
196
+ let scale = 1;
197
+ const newImgDataState = {} as ImageDataState;
198
+ /* 计算图片加载后的初始显示尺寸 */
199
+ if (naturalWidth / containerWidth > naturalHeight / containerHeight) {
200
+ scale = containerWidth / naturalWidth;
201
+ newImgDataState.width = containerWidth;
202
+ newImgDataState.height = naturalHeight * scale;
203
+ } else {
204
+ scale = containerHeight / naturalHeight;
205
+ newImgDataState.width = naturalWidth * scale;
206
+ newImgDataState.height = containerHeight;
207
+ }
208
+ this.imgData.scale = scale;
209
+ newImgDataState.centerPoint = {} as Point;
210
+ newImgDataState.centerPoint.x = containerWidth / 2;
211
+ newImgDataState.centerPoint.y = containerHeight / 2;
212
+ /* 计算裁切框大小 */
213
+ const newCropperBoxState = {} as CropperBox;
214
+ const { defaultAspectRatio, aspectRatio } = this.getProps();
215
+ const calcAspect = aspectRatio || defaultAspectRatio;
216
+ if (containerWidth / containerHeight > calcAspect) {
217
+ newCropperBoxState.width = containerHeight * calcAspect;
218
+ newCropperBoxState.height = containerHeight;
219
+ } else {
220
+ newCropperBoxState.width = containerWidth;
221
+ newCropperBoxState.height = containerWidth / calcAspect;
222
+ }
223
+ newCropperBoxState.centerPoint = {} as Point;
224
+ newCropperBoxState.centerPoint.x = containerWidth / 2;
225
+ newCropperBoxState.centerPoint.y = containerHeight / 2;
226
+ this.setState({
227
+ imgData: newImgDataState,
228
+ cropperBox: newCropperBoxState,
229
+ loaded: true,
230
+ } as any);
231
+ }
232
+
233
+ handleWheel = (e: any) => {
234
+ // 防止双手缩放导致页面被放大
235
+ e.preventDefault();
236
+ const { imgData, zoom: currZoom } = this.getStates();
237
+ const { maxZoom, minZoom, zoomStep } = this.getProps();
238
+
239
+ let _zoom: number;
240
+ if (e.deltaY < 0) {
241
+ /* zoom in */
242
+ if (currZoom + zoomStep <= maxZoom) {
243
+ _zoom = Number((currZoom + zoomStep).toFixed(2));
244
+ }
245
+ } else if (e.deltaY > 0) {
246
+ /* zoom out */
247
+ if (currZoom - zoomStep >= minZoom) {
248
+ _zoom = Number((currZoom - zoomStep).toFixed(2));
249
+ }
250
+ }
251
+ if (_zoom === undefined) {
252
+ return;
253
+ }
254
+ const boundingRect = e.currentTarget.getBoundingClientRect();
255
+ const offsetX = e.clientX - boundingRect.left;
256
+ const offsetY = e.clientY - boundingRect.top;
257
+ const scaleCenter = {
258
+ x: offsetX,
259
+ y: - offsetY,
260
+ };
261
+
262
+ // 计算新的中心点位置
263
+ const currentPoint = { ...imgData.centerPoint } as Point;
264
+ currentPoint.y = - currentPoint.y;
265
+
266
+ const newCenterPoint = {
267
+ x: (currentPoint.x - scaleCenter.x) / currZoom * _zoom + scaleCenter.x,
268
+ y: - [(currentPoint.y - scaleCenter.y) / currZoom * _zoom + scaleCenter.y],
269
+ };
270
+
271
+ const newWidth = imgData.width / currZoom * _zoom;
272
+ const newHeight = imgData.height / currZoom * _zoom;
273
+
274
+ const newImgDataState = {
275
+ width: newWidth,
276
+ height: newHeight,
277
+ centerPoint: newCenterPoint
278
+ };
279
+ this.setState({
280
+ imgData: newImgDataState,
281
+ zoom: _zoom
282
+ } as any);
283
+
284
+ this._adapter.notifyZoomChange(_zoom);
285
+ }
286
+
287
+ getMoveParamByDir(dir: string) {
288
+ let paramX = 0, paramY = 0;
289
+ switch (dir) {
290
+ case 'tl':
291
+ paramX = -1; paramY = -1; break;
292
+ case 'tm':
293
+ paramY = -1; break;
294
+ case 'tr':
295
+ paramX = 1; paramY = -1; break;
296
+ case 'ml':
297
+ paramX = -1; break;
298
+ case 'mr':
299
+ paramX = 1; break;
300
+ case 'bl':
301
+ paramX = -1; paramY = 1; break;
302
+ case 'bm':
303
+ paramY = 1; break;
304
+ case 'br':
305
+ paramX = 1; paramY = 1; break;
306
+ default:
307
+ break;
308
+ }
309
+ return {
310
+ paramX,
311
+ paramY
312
+ };
313
+ }
314
+
315
+ getRangeForAspectChange = () => {
316
+ const { cropperBox } = this.getStates();
317
+ const { aspectRatio } = this.getProps();
318
+ const { width: containerWidth, height: containerHeight } = this.containerData;
319
+ // 可能的最大宽高
320
+ let height: number, width: number;
321
+ // 裁剪框当前的位置
322
+ const xMin = cropperBox.centerPoint.x - cropperBox.width / 2;
323
+ const xMax = cropperBox.centerPoint.x + cropperBox.width / 2;
324
+ const yMin = cropperBox.centerPoint.y - cropperBox.height / 2;
325
+ const yMax = cropperBox.centerPoint.y + cropperBox.height / 2;
326
+ switch (this.boxMoveDir) {
327
+ case 'tl':
328
+ height = yMax;
329
+ width = xMax;
330
+ [width, height] = getAspectHW(width, height, aspectRatio);
331
+ this.rangeX = [xMax - width, xMax];
332
+ this.rangeY = [yMax - height, yMax];
333
+ break;
334
+ case 'tm':
335
+ height = yMax;
336
+ const leftHalfWidth = cropperBox.centerPoint.x;
337
+ const rightHalfWidth = containerWidth - cropperBox.centerPoint.x;
338
+ width = 2 * (leftHalfWidth < rightHalfWidth ? leftHalfWidth : rightHalfWidth);
339
+ [width, height] = getAspectHW(width, height, aspectRatio);
340
+ this.rangeX = [
341
+ cropperBox.centerPoint.x - width / 2,
342
+ cropperBox.centerPoint.x + width / 2
343
+ ];
344
+ this.rangeY = [yMax - height, yMax];
345
+ break;
346
+ case 'tr':
347
+ height = yMax;
348
+ width = containerWidth - xMin;
349
+ [width, height] = getAspectHW(width, height, aspectRatio);
350
+ this.rangeX = [xMin, xMin + width];
351
+ this.rangeY = [yMax - height, yMax];
352
+ break;
353
+ case 'ml':
354
+ width = xMax;
355
+ const topHalfHeight = cropperBox.centerPoint.y;
356
+ const bottomHalfHeight = containerHeight - cropperBox.centerPoint.y;
357
+ height = 2 * (topHalfHeight < bottomHalfHeight ? topHalfHeight : bottomHalfHeight);
358
+ [width, height] = getAspectHW(width, height, aspectRatio);
359
+ this.rangeX = [xMax - width, xMax];
360
+ this.rangeY = [
361
+ cropperBox.centerPoint.y - height / 2,
362
+ cropperBox.centerPoint.y + height / 2
363
+ ];
364
+ break;
365
+ case 'mr':
366
+ width = containerWidth - xMin;
367
+ const topHalfHeight2 = cropperBox.centerPoint.y;
368
+ const bottomHalfHeight2 = containerHeight - cropperBox.centerPoint.y;
369
+ height = 2 * (topHalfHeight2 < bottomHalfHeight2 ? topHalfHeight2 : bottomHalfHeight2);
370
+ [width, height] = getAspectHW(width, height, aspectRatio);
371
+ this.rangeX = [xMin, xMin + width];
372
+ this.rangeY = [
373
+ cropperBox.centerPoint.y - height / 2,
374
+ cropperBox.centerPoint.y + height / 2
375
+ ];
376
+ break;
377
+ case 'bl':
378
+ height = containerHeight - yMin;
379
+ width = xMax;
380
+ [width, height] = getAspectHW(width, height, aspectRatio);
381
+ this.rangeX = [xMax - width, xMax];
382
+ this.rangeY = [yMin, yMin + height];
383
+ break;
384
+ case 'bm':
385
+ height = containerHeight - yMin;
386
+ const leftHalfWidth2 = cropperBox.centerPoint.x;
387
+ const rightHalfWidth2 = containerWidth - cropperBox.centerPoint.x;
388
+ width = 2 * (leftHalfWidth2 < rightHalfWidth2 ? leftHalfWidth2 : rightHalfWidth2);
389
+ [width, height] = getAspectHW(width, height, aspectRatio);
390
+ this.rangeX = [
391
+ cropperBox.centerPoint.x - width / 2,
392
+ cropperBox.centerPoint.x + width / 2,
393
+ ];
394
+ this.rangeY = [yMin, yMin + height];
395
+ break;
396
+ case 'br':
397
+ height = containerHeight - yMin;
398
+ width = containerWidth - xMin;
399
+ [width, height] = getAspectHW(width, height, aspectRatio);
400
+ this.rangeX = [xMin, xMin + width];
401
+ this.rangeY = [yMin, yMin + height];
402
+ break;
403
+ default:
404
+ break;
405
+ }
406
+ }
407
+
408
+ handleCornerMouseDown = (e: any) => {
409
+ const currentTarget = e.currentTarget;
410
+ if (!currentTarget) {
411
+ return;
412
+ }
413
+ e.preventDefault();
414
+ const dir = currentTarget.dataset.dir;
415
+ this.boxMoveDir = dir;
416
+ this.boxMoveParam = this.getMoveParamByDir(dir);
417
+ this.bindResizeEvent();
418
+ const { aspectRatio } = this.getProps();
419
+ if (aspectRatio) {
420
+ this.getRangeForAspectChange();
421
+ } else {
422
+ this.rangeX = [0, this.containerData.width];
423
+ this.rangeY = [0, this.containerData.height];
424
+ }
425
+
426
+ }
427
+
428
+ bindResizeEvent = () => {
429
+ const { aspectRatio } = this.getProps();
430
+ document.addEventListener('mousemove', aspectRatio ? this.handleCornerAspectMouseMove : this.handleCornerMouseMove);
431
+ document.addEventListener('mouseup', this.handleCornerMouseUp);
432
+ }
433
+
434
+ unBindResizeEvent = () => {
435
+ const { aspectRatio } = this.getProps();
436
+ document.removeEventListener('mousemove', aspectRatio ? this.handleCornerAspectMouseMove : this.handleCornerMouseMove);
437
+ document.removeEventListener('mouseup', this.handleCornerMouseUp);
438
+ }
439
+
440
+ viewIMGDragStart = (e: any) => {
441
+ e.preventDefault();
442
+ }
443
+
444
+ handleCornerAspectMouseMove = (e: any) => {
445
+ e.preventDefault();
446
+ const { clientX, clientY } = e;
447
+ const { cropperBox } = this.getStates();
448
+ const { aspectRatio } = this.getProps();
449
+ const boundingRect = this._adapter.getContainer().getBoundingClientRect();
450
+ const newCropperBoxPos = {
451
+ width: cropperBox.width,
452
+ height: cropperBox.height,
453
+ centerPoint: { ...cropperBox.centerPoint }
454
+ };
455
+ let offsetX: number, offsetY: number;
456
+ if (['ml', 'mr'].includes(this.boxMoveDir)) {
457
+ offsetX = getMiddle(clientX - boundingRect.left, this.rangeX);
458
+ } else {
459
+ offsetY = getMiddle(clientY - boundingRect.top, this.rangeY);
460
+ }
461
+ switch (this.boxMoveDir) {
462
+ case 'tl':
463
+ newCropperBoxPos.height = this.rangeY[1] - offsetY;
464
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
465
+ newCropperBoxPos.centerPoint = {
466
+ x: this.rangeX[1] - newCropperBoxPos.width / 2,
467
+ y: this.rangeY[1] - newCropperBoxPos.height / 2,
468
+ };
469
+ break;
470
+ case 'tm':
471
+ newCropperBoxPos.height = this.rangeY[1] - offsetY;
472
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
473
+ newCropperBoxPos.centerPoint = {
474
+ x: cropperBox.centerPoint.x,
475
+ y: this.rangeY[1] - newCropperBoxPos.height / 2,
476
+ };
477
+ break;
478
+ case 'tr':
479
+ newCropperBoxPos.height = this.rangeY[1] - offsetY;
480
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
481
+ newCropperBoxPos.centerPoint = {
482
+ x: this.rangeX[0] + newCropperBoxPos.width / 2,
483
+ y: this.rangeY[1] - newCropperBoxPos.height / 2,
484
+ };
485
+ break;
486
+ case 'ml':
487
+ newCropperBoxPos.width = this.rangeX[1] - offsetX;
488
+ newCropperBoxPos.height = newCropperBoxPos.width / aspectRatio;
489
+ newCropperBoxPos.centerPoint = {
490
+ x: this.rangeX[1] - newCropperBoxPos.width / 2,
491
+ y: cropperBox.centerPoint.y,
492
+ };
493
+ break;
494
+ case 'mr':
495
+ newCropperBoxPos.width = offsetX - this.rangeX[0];
496
+ newCropperBoxPos.height = newCropperBoxPos.width / aspectRatio;
497
+ newCropperBoxPos.centerPoint = {
498
+ x: this.rangeX[0] + newCropperBoxPos.width / 2,
499
+ y: cropperBox.centerPoint.y,
500
+ };
501
+ break;
502
+ case 'bl':
503
+ newCropperBoxPos.height = offsetY - this.rangeY[0];
504
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
505
+ newCropperBoxPos.centerPoint = {
506
+ x: this.rangeX[1] - newCropperBoxPos.width / 2,
507
+ y: this.rangeY[0] + newCropperBoxPos.height / 2,
508
+ };
509
+ break;
510
+ case 'bm':
511
+ newCropperBoxPos.height = offsetY - this.rangeY[0];
512
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
513
+ newCropperBoxPos.centerPoint = {
514
+ x: cropperBox.centerPoint.x,
515
+ y: this.rangeY[0] + newCropperBoxPos.height / 2,
516
+ };
517
+ break;
518
+ case 'br':
519
+ newCropperBoxPos.height = offsetY - this.rangeY[0];
520
+ newCropperBoxPos.width = newCropperBoxPos.height * aspectRatio;
521
+ newCropperBoxPos.centerPoint = {
522
+ x: this.rangeX[0] + newCropperBoxPos.width / 2,
523
+ y: this.rangeY[0] + newCropperBoxPos.height / 2,
524
+ };
525
+ break;
526
+ default:
527
+ break;
528
+ }
529
+ if (newCropperBoxPos.height === 0 && newCropperBoxPos.width === 0) {
530
+ this.changeDir();
531
+ this.getRangeForAspectChange();
532
+ }
533
+ this.setState({
534
+ cropperBox: newCropperBoxPos
535
+ } as any);
536
+ }
537
+
538
+ changeDir = () => {
539
+ if (this.boxMoveDir.includes('t')) {
540
+ this.boxMoveDir = this.boxMoveDir.replace('t', 'b');
541
+ } else if (this.boxMoveDir.includes('b')) {
542
+ this.boxMoveDir = this.boxMoveDir.replace('b', 't');
543
+ }
544
+ if (this.boxMoveDir.includes('l')) {
545
+ this.boxMoveDir = this.boxMoveDir.replace('l', 'r');
546
+ } else if (this.boxMoveDir.includes('r')) {
547
+ this.boxMoveDir = this.boxMoveDir.replace('r', 'l');
548
+ }
549
+ }
550
+
551
+ handleCornerMouseMove = (e: any) => {
552
+ e.preventDefault();
553
+ const { clientX, clientY } = e;
554
+ const { cropperBox } = this.getStates();
555
+ const boundingRect = this._adapter.getContainer().getBoundingClientRect();
556
+ let offsetX = getMiddle(clientX - boundingRect.left, this.rangeX);
557
+ let offsetY = getMiddle(clientY - boundingRect.top, this.rangeY);
558
+ const newCropperBoxPos = {
559
+ width: cropperBox.width,
560
+ height: cropperBox.height,
561
+ centerPoint: {
562
+ x: cropperBox.centerPoint.x,
563
+ y: cropperBox.centerPoint.y
564
+ }
565
+ };
566
+ const { paramX, paramY } = this.boxMoveParam;
567
+ let x: number, y: number;
568
+ if (paramX) {
569
+ x = cropperBox.centerPoint.x + paramX * cropperBox.width / 2;
570
+ newCropperBoxPos.width = cropperBox.width + paramX * (offsetX - x);
571
+ if (newCropperBoxPos.width < 0) {
572
+ newCropperBoxPos.width = - newCropperBoxPos.width;
573
+ this.boxMoveParam.paramX = -paramX;
574
+ }
575
+ newCropperBoxPos.centerPoint.x = offsetX - paramX * newCropperBoxPos.width / 2;
576
+ }
577
+ if (paramY) {
578
+ y = cropperBox.centerPoint.y + paramY * cropperBox.height / 2;
579
+ newCropperBoxPos.height = cropperBox.height + paramY * (offsetY - y);
580
+ if (newCropperBoxPos.height < 0) {
581
+ newCropperBoxPos.height = -newCropperBoxPos.height;
582
+ this.boxMoveParam.paramY = -paramY;
583
+ }
584
+ newCropperBoxPos.centerPoint.y = offsetY - paramY * newCropperBoxPos.height / 2;
585
+ }
586
+
587
+ this.setState({
588
+ cropperBox: newCropperBoxPos
589
+ } as any);
590
+ }
591
+
592
+ handleCornerMouseUp = (e: any) => {
593
+ this.boxMoveParam = { paramX: 0, paramY: 0 };
594
+ this.unBindResizeEvent();
595
+ }
596
+
597
+ handleCropperBoxMouseDown = (e: any) => {
598
+ const target = e.target;
599
+ const { cropperBox } = this.getStates();
600
+ const container = this._adapter.getContainer();
601
+ const boundingRect = container.getBoundingClientRect();
602
+ if (target.dataset.dir) {
603
+ // 如果鼠标是落在了corner上,那么不做任何操作
604
+ return;
605
+ }
606
+ // 移动裁切框
607
+ this.cropperBoxMoveStart = {
608
+ x: e.clientX,
609
+ y: e.clientY
610
+ };
611
+ this.bindMoveEvent();
612
+ // 计算 cropperBox 中心点移动范围
613
+ this.moveRange = {
614
+ xMin: cropperBox.width / 2,
615
+ xMax: boundingRect.width - cropperBox.width / 2,
616
+ yMin: cropperBox.height / 2,
617
+ yMax: boundingRect.height - cropperBox.height / 2,
618
+ };
619
+ }
620
+
621
+ bindMoveEvent = () => {
622
+ document.addEventListener('mousemove', this.handleCropperBoxMouseMove);
623
+ document.addEventListener('mouseup', this.handleCropperBoxMouseUp);
624
+ }
625
+
626
+ unBindMoveEvent = () => {
627
+ document.removeEventListener('mousemove', this.handleCropperBoxMouseMove);
628
+ document.removeEventListener('mouseup', this.handleCropperBoxMouseUp);
629
+ }
630
+
631
+ handleCropperBoxMouseMove = (e: any) => {
632
+ if (!this.cropperBoxMoveStart) {
633
+ return;
634
+ }
635
+ const { clientX, clientY } = e;
636
+ const { cropperBox } = this.getStates();
637
+ const offsetX = clientX - this.cropperBoxMoveStart.x;
638
+ const offsetY = clientY - this.cropperBoxMoveStart.y;
639
+ const newCenterPointX = getMiddle(cropperBox.centerPoint.x + offsetX, [this.moveRange.xMin, this.moveRange.xMax]);
640
+ const newCenterPointY = getMiddle(cropperBox.centerPoint.y + offsetY, [this.moveRange.yMin, this.moveRange.yMax]);
641
+ const newCropperBoxPos = {
642
+ width: cropperBox.width,
643
+ height: cropperBox.height,
644
+ centerPoint: {
645
+ x: newCenterPointX,
646
+ y: newCenterPointY
647
+ }
648
+ };
649
+ this.cropperBoxMoveStart = {
650
+ x: clientX,
651
+ y: clientY
652
+ };
653
+ this.setState({
654
+ cropperBox: newCropperBoxPos
655
+ } as any);
656
+ }
657
+
658
+ handleCropperBoxMouseUp = (e: any) => {
659
+ if (!this.cropperBoxMoveStart) {
660
+ return;
661
+ }
662
+ this.cropperBoxMoveStart = null;
663
+ this.unBindMoveEvent();
664
+ }
665
+
666
+ handleMaskMouseDown = (e: any) => {
667
+ if (e.currentTarget !== e.target) {
668
+ return;
669
+ }
670
+ this.bindImgMoveEvent();
671
+ // 记录开始移动的位置
672
+ this.imgMoveStart = {
673
+ x: e.clientX,
674
+ y: e.clientY
675
+ };
676
+ }
677
+
678
+ bindImgMoveEvent = () => {
679
+ document.addEventListener('mousemove', this.handleImgMove);
680
+ document.addEventListener('mouseup', this.handleImgMoveUp);
681
+ }
682
+
683
+ unBindImgMoveEvent = () => {
684
+ document.removeEventListener('mousemove', this.handleImgMove);
685
+ document.removeEventListener('mouseup', this.handleImgMoveUp);
686
+ }
687
+
688
+ handleImgMove = (e: any) => {
689
+ if (!this.imgMoveStart) {
690
+ return;
691
+ }
692
+ const { clientX, clientY } = e;
693
+ const { imgData } = this.getStates();
694
+ const offsetX = clientX - this.imgMoveStart.x;
695
+ const offsetY = clientY - this.imgMoveStart.y;
696
+ const newCenterPointX = imgData.centerPoint.x + offsetX;
697
+ const newCenterPointY = imgData.centerPoint.y + offsetY;
698
+ const newImgData = {
699
+ width: imgData.width,
700
+ height: imgData.height,
701
+ centerPoint: {
702
+ x: newCenterPointX,
703
+ y: newCenterPointY
704
+ }
705
+ };
706
+ this.imgMoveStart = {
707
+ x: clientX,
708
+ y: clientY
709
+ };
710
+ this.setState({
711
+ imgData: newImgData
712
+ } as any);
713
+ }
714
+
715
+ handleImgMoveUp = (e: any) => {
716
+ if (!this.imgMoveStart) {
717
+ return;
718
+ }
719
+ this.imgMoveStart = null;
720
+ this.unBindImgMoveEvent();
721
+ }
722
+
723
+ getCropperCanvas = () => {
724
+ const { cropperBox, imgData, rotate, zoom } = this.getStates();
725
+ const { fill } = this.getProps();
726
+ const canvas = document.createElement('canvas');
727
+ const ctx = canvas.getContext('2d');
728
+ const img = this._adapter.getImg();
729
+
730
+ // 计算包含旋转后的图片的矩形容器的宽高
731
+ const angle = rotate * Math.PI / 180;
732
+ const sine = Math.abs(Math.sin(angle));
733
+ const cosine = Math.abs(Math.cos(angle));
734
+ const imgWidth = this.imgData.originalWidth;
735
+ const imgHeight = this.imgData.originalHeight;
736
+ const containerWidth = imgWidth * cosine + imgHeight * sine;
737
+ const containerHeight = imgHeight * cosine + imgWidth * sine;
738
+
739
+ // 判断裁切区域和外接矩形是否存在交集,如果不存在,则直接返回空白图片
740
+ // 计算需要裁剪的区域实际大小和位置
741
+ const cropperContainerWidth = containerWidth * zoom * this.imgData.scale;
742
+ const cropperContainerHeight = containerHeight * zoom * this.imgData.scale;
743
+ const cropperContainerTop = imgData.centerPoint.y - cropperContainerHeight / 2;
744
+ const cropperContainerLeft = imgData.centerPoint.x - cropperContainerWidth / 2;
745
+ const cropperBoxLeft = cropperBox.centerPoint.x - cropperBox.width / 2;
746
+ const cropperBoxTop = cropperBox.centerPoint.y - cropperBox.height / 2;
747
+ const realZoom = zoom * this.imgData.scale;
748
+
749
+ const relativeCropLeft = (cropperBoxLeft - cropperContainerLeft) / realZoom;
750
+ const relativeCropTop = (cropperBoxTop - cropperContainerTop) / realZoom;
751
+ const relativeWidth = cropperBox.width / realZoom;
752
+ const relativeHeight = cropperBox.height / realZoom;
753
+ const relativeCropRight = relativeCropLeft + relativeWidth;
754
+ const relativeCropBottom = relativeCropTop + relativeHeight;
755
+
756
+ if (relativeCropRight < 0 || relativeCropBottom < 0 || relativeCropLeft > containerWidth || relativeCropTop > containerHeight) {
757
+ // 没有交集,直接返回空白图片
758
+ const emptyCanvas = document.createElement('canvas');
759
+ const ctx = emptyCanvas.getContext('2d');
760
+ emptyCanvas.width = relativeWidth;
761
+ emptyCanvas.height = relativeHeight;
762
+ ctx.fillStyle = fill;
763
+ ctx.fillRect(0, 0, relativeWidth, relativeHeight);
764
+ return emptyCanvas;
765
+ }
766
+
767
+ canvas.width = containerWidth;
768
+ canvas.height = containerHeight;
769
+ ctx.fillStyle = fill;
770
+ ctx.fillRect(0, 0, containerWidth, containerHeight);
771
+
772
+ const halfWidth = containerWidth / 2;
773
+ const halfHeight = containerHeight / 2;
774
+ ctx.translate(halfWidth, halfHeight);
775
+ ctx.rotate(rotate * Math.PI / 180);
776
+ ctx.translate(-halfWidth, -halfHeight);
777
+
778
+ const imgX = (containerWidth - imgWidth) / 2;
779
+ const imgY = (containerHeight - imgHeight) / 2;
780
+ ctx.drawImage(img, 0, 0, imgWidth, imgHeight, imgX, imgY, imgWidth, imgHeight);
781
+
782
+ const canvas2 = document.createElement('canvas');
783
+ const ctx2 = canvas2.getContext('2d');
784
+ // 为了避免裁剪时候,超出被裁切的画布的部分颜色不正常,需要将裁切区域限制在画布范围内。
785
+ // 相对位置会在后续进行修正
786
+ let realLeft = relativeCropLeft;
787
+ let realTop = relativeCropTop;
788
+ let realWidth = relativeWidth;
789
+ let realHeight = relativeHeight;
790
+
791
+ if (relativeCropLeft < 0) {
792
+ realLeft = 0;
793
+ }
794
+ if (relativeCropTop < 0) {
795
+ realTop = 0;
796
+ }
797
+ if (relativeCropRight > containerWidth) {
798
+ realWidth = containerWidth - realLeft;
799
+ } else if (relativeCropLeft < 0) {
800
+ realWidth = relativeCropRight;
801
+ }
802
+
803
+ if (relativeCropBottom > containerHeight) {
804
+ realHeight = containerHeight - realTop;
805
+ } else if (relativeCropTop < 0) {
806
+ realHeight = relativeCropBottom;
807
+ }
808
+
809
+ const imgDataResult = ctx.getImageData(realLeft, realTop, realWidth, realHeight);
810
+ canvas2.width = relativeWidth;
811
+ canvas2.height = relativeHeight;
812
+ ctx2.fillStyle = fill;
813
+ ctx2.fillRect(0, 0, relativeWidth, relativeHeight);
814
+ ctx2.putImageData(
815
+ imgDataResult,
816
+ relativeCropLeft < 0 ? - relativeCropLeft : 0,
817
+ relativeCropTop < 0 ? - relativeCropTop : 0,
818
+ );
819
+ return canvas2;
820
+ }
821
+ }