@h4md1/visual-image-tool 0.2.0 → 0.2.1
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/README.md +65 -37
- package/dist/visual-image-tool.esm.js +921 -836
- package/dist/visual-image-tool.esm.js.map +1 -1
- package/dist/visual-image-tool.js +921 -836
- package/dist/visual-image-tool.js.map +1 -1
- package/dist/visual-image-tool.umd.js +1 -1
- package/dist/visual-image-tool.umd.js.map +1 -1
- package/package.json +48 -42
- package/src/index.js +1 -1
- package/src/visual-image-tool.js +924 -840
|
@@ -8,842 +8,927 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
class VisualImageTool {
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Crée une instance de l'outil d'image
|
|
13
|
+
* @param {Object} options - Options de configuration
|
|
14
|
+
* @param {HTMLElement|string} options.imageElement - Élément image ou sélecteur CSS
|
|
15
|
+
* @param {Object} [options.focusPoint] - Configuration du point focal
|
|
16
|
+
* @param {boolean} [options.focusPoint.enabled=true] - Activer la fonctionnalité de point focal
|
|
17
|
+
* @param {Object} [options.focusPoint.style] - Styles personnalisés pour le marqueur de point focal
|
|
18
|
+
* @param {Object} [options.cropZone] - Configuration de la zone de recadrage
|
|
19
|
+
* @param {boolean} [options.cropZone.enabled=true] - Activer la fonctionnalité de zone de recadrage
|
|
20
|
+
* @param {Object} [options.cropZone.style] - Styles personnalisés pour l'overlay de recadrage
|
|
21
|
+
* @param {Function} [options.onChange] - Callback appelé lors des changements
|
|
22
|
+
*/
|
|
23
|
+
constructor(options) {
|
|
24
|
+
// Valider les options
|
|
25
|
+
if (!options || !options.imageElement) {
|
|
26
|
+
throw new Error("L'élément image est requis");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Initialiser les propriétés
|
|
30
|
+
this.imageElement =
|
|
31
|
+
typeof options.imageElement === "string"
|
|
32
|
+
? document.querySelector(options.imageElement)
|
|
33
|
+
: options.imageElement;
|
|
34
|
+
|
|
35
|
+
if (!this.imageElement || this.imageElement.tagName !== "IMG") {
|
|
36
|
+
throw new Error("Élément image invalide");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Options par défaut
|
|
40
|
+
this.options = {
|
|
41
|
+
focusPoint: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
style: {
|
|
44
|
+
width: "30px",
|
|
45
|
+
height: "30px",
|
|
46
|
+
border: "3px solid white",
|
|
47
|
+
boxShadow: "0 0 0 2px black, 0 0 5px rgba(0,0,0,0.5)",
|
|
48
|
+
backgroundColor: "rgba(255, 0, 0, 0.5)",
|
|
49
|
+
},
|
|
50
|
+
...options.focusPoint,
|
|
51
|
+
},
|
|
52
|
+
cropZone: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
style: {
|
|
55
|
+
border: "1px dashed #fff",
|
|
56
|
+
backgroundColor: "rgba(0, 0, 0, 0.4)",
|
|
57
|
+
},
|
|
58
|
+
handleStyle: {
|
|
59
|
+
width: "14px",
|
|
60
|
+
height: "14px",
|
|
61
|
+
backgroundColor: "white",
|
|
62
|
+
border: "2px solid black",
|
|
63
|
+
boxShadow: "0 0 3px rgba(0,0,0,0.5)",
|
|
64
|
+
},
|
|
65
|
+
...options.cropZone,
|
|
66
|
+
},
|
|
67
|
+
onChange: options.onChange || (() => {}),
|
|
68
|
+
debug: options.debug || false,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// État interne
|
|
72
|
+
this.state = {
|
|
73
|
+
focusMarker: null,
|
|
74
|
+
cropOverlay: null,
|
|
75
|
+
focusActive: false,
|
|
76
|
+
cropActive: false,
|
|
77
|
+
focusPoint: { x: 0, y: 0 },
|
|
78
|
+
cropZone: { x: 0, y: 0, width: 0, height: 0 },
|
|
79
|
+
originalWidth: 1,
|
|
80
|
+
originalHeight: 1,
|
|
81
|
+
displayWidth: 1,
|
|
82
|
+
displayHeight: 1,
|
|
83
|
+
scaleX: 1,
|
|
84
|
+
scaleY: 1,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Variables pour le suivi des interactions
|
|
88
|
+
this.interaction = {
|
|
89
|
+
focusDragging: false,
|
|
90
|
+
focusDragOffsetX: 0,
|
|
91
|
+
focusDragOffsetY: 0,
|
|
92
|
+
cropDragging: false,
|
|
93
|
+
cropResizing: false,
|
|
94
|
+
activeHandle: null,
|
|
95
|
+
startX: 0,
|
|
96
|
+
startY: 0,
|
|
97
|
+
startWidth: 0,
|
|
98
|
+
startHeight: 0,
|
|
99
|
+
startMouseX: 0,
|
|
100
|
+
startMouseY: 0,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Initialiser l'outil
|
|
104
|
+
this._init();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Initialise l'outil d'image
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
_init() {
|
|
112
|
+
// Préparer le conteneur parent
|
|
113
|
+
this._prepareContainer();
|
|
114
|
+
|
|
115
|
+
// Initialiser les dimensions
|
|
116
|
+
this._updateScaling();
|
|
117
|
+
|
|
118
|
+
// Créer les éléments d'interface si activés
|
|
119
|
+
if (this.options.focusPoint.enabled) {
|
|
120
|
+
this._createFocusMarker();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.options.cropZone.enabled) {
|
|
124
|
+
this._createCropOverlay();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Ajouter les écouteurs d'événements
|
|
128
|
+
this._setupEventListeners();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Prépare le conteneur parent de l'image
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
_prepareContainer() {
|
|
136
|
+
// S'assurer que le parent est positionné
|
|
137
|
+
if (this.imageElement.parentNode) {
|
|
138
|
+
this.imageElement.parentNode.style.position = "relative";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Met à jour les facteurs d'échelle
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
_updateScaling() {
|
|
147
|
+
this.state.originalWidth = this.imageElement.naturalWidth || 1;
|
|
148
|
+
this.state.originalHeight = this.imageElement.naturalHeight || 1;
|
|
149
|
+
this.state.displayWidth = this.imageElement.offsetWidth;
|
|
150
|
+
this.state.displayHeight = this.imageElement.offsetHeight;
|
|
151
|
+
this.state.scaleX = this.state.displayWidth / this.state.originalWidth;
|
|
152
|
+
this.state.scaleY = this.state.displayHeight / this.state.originalHeight;
|
|
153
|
+
|
|
154
|
+
// Repositionner les éléments si actifs
|
|
155
|
+
if (this.state.focusActive) {
|
|
156
|
+
this._updateFocusMarkerPosition();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (this.state.cropActive) {
|
|
160
|
+
this._updateCropOverlayPosition();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Convertit des coordonnées d'affichage en coordonnées originales
|
|
166
|
+
* @private
|
|
167
|
+
* @param {number} scaledX - Coordonnée X à l'échelle d'affichage
|
|
168
|
+
* @param {number} scaledY - Coordonnée Y à l'échelle d'affichage
|
|
169
|
+
* @returns {Object} Coordonnées originales
|
|
170
|
+
*/
|
|
171
|
+
_toOriginalCoords(scaledX, scaledY) {
|
|
172
|
+
return {
|
|
173
|
+
x: scaledX / this.state.scaleX,
|
|
174
|
+
y: scaledY / this.state.scaleY,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Convertit des coordonnées originales en coordonnées d'affichage
|
|
180
|
+
* @private
|
|
181
|
+
* @param {number} originalX - Coordonnée X originale
|
|
182
|
+
* @param {number} originalY - Coordonnée Y originale
|
|
183
|
+
* @returns {Object} Coordonnées à l'échelle d'affichage
|
|
184
|
+
*/
|
|
185
|
+
_toScaledCoords(originalX, originalY) {
|
|
186
|
+
return {
|
|
187
|
+
x: originalX * this.state.scaleX,
|
|
188
|
+
y: originalY * this.state.scaleY,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Crée le marqueur de point focal
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_createFocusMarker() {
|
|
197
|
+
if (this.state.focusMarker) return;
|
|
198
|
+
|
|
199
|
+
const marker = document.createElement("div");
|
|
200
|
+
marker.style.position = "absolute";
|
|
201
|
+
marker.style.width = this.options.focusPoint.style.width;
|
|
202
|
+
marker.style.height = this.options.focusPoint.style.height;
|
|
203
|
+
marker.style.border = this.options.focusPoint.style.border;
|
|
204
|
+
marker.style.boxShadow = this.options.focusPoint.style.boxShadow;
|
|
205
|
+
marker.style.backgroundColor =
|
|
206
|
+
this.options.focusPoint.style.backgroundColor;
|
|
207
|
+
marker.style.cursor = "move";
|
|
208
|
+
marker.style.display = "none";
|
|
209
|
+
marker.style.zIndex = "999";
|
|
210
|
+
marker.style.clipPath =
|
|
211
|
+
"polygon(40% 0%, 60% 0%, 60% 40%, 100% 40%, 100% 60%, 60% 60%, 60% 100%, 40% 100%, 40% 60%, 0% 60%, 0% 40%, 40% 40%)";
|
|
212
|
+
|
|
213
|
+
this.imageElement.parentNode.appendChild(marker);
|
|
214
|
+
this.state.focusMarker = marker;
|
|
215
|
+
|
|
216
|
+
// Ajouter les écouteurs d'événements au marqueur
|
|
217
|
+
marker.addEventListener(
|
|
218
|
+
"mousedown",
|
|
219
|
+
this._handleFocusMarkerMouseDown.bind(this),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Gère l'événement mousedown sur le marqueur de point focal
|
|
225
|
+
* @private
|
|
226
|
+
* @param {MouseEvent} e - Événement mousedown
|
|
227
|
+
*/
|
|
228
|
+
_handleFocusMarkerMouseDown(e) {
|
|
229
|
+
this.interaction.focusDragging = true;
|
|
230
|
+
this.state.focusMarker.style.cursor = "grabbing";
|
|
231
|
+
|
|
232
|
+
// Calculer le décalage par rapport au centre du marqueur
|
|
233
|
+
const rect = this.state.focusMarker.getBoundingClientRect();
|
|
234
|
+
this.interaction.focusDragOffsetX =
|
|
235
|
+
e.clientX - (rect.left + rect.width / 2);
|
|
236
|
+
this.interaction.focusDragOffsetY =
|
|
237
|
+
e.clientY - (rect.top + rect.height / 2);
|
|
238
|
+
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Crée l'overlay de zone de recadrage
|
|
244
|
+
* @private
|
|
245
|
+
*/
|
|
246
|
+
_createCropOverlay() {
|
|
247
|
+
if (this.state.cropOverlay) return;
|
|
248
|
+
|
|
249
|
+
const overlay = document.createElement("div");
|
|
250
|
+
overlay.style.position = "absolute";
|
|
251
|
+
overlay.style.border = this.options.cropZone.style.border;
|
|
252
|
+
overlay.style.backgroundColor = this.options.cropZone.style.backgroundColor;
|
|
253
|
+
overlay.style.boxSizing = "border-box";
|
|
254
|
+
overlay.style.cursor = "move";
|
|
255
|
+
overlay.style.display = "none";
|
|
256
|
+
overlay.style.zIndex = "998";
|
|
257
|
+
|
|
258
|
+
// Ajouter les poignées de redimensionnement
|
|
259
|
+
const handles = ["tl", "tm", "tr", "ml", "mr", "bl", "bm", "br"];
|
|
260
|
+
for (const handleType of handles) {
|
|
261
|
+
const handle = document.createElement("div");
|
|
262
|
+
handle.style.position = "absolute";
|
|
263
|
+
handle.style.width = this.options.cropZone.handleStyle.width;
|
|
264
|
+
handle.style.height = this.options.cropZone.handleStyle.height;
|
|
265
|
+
handle.style.backgroundColor =
|
|
266
|
+
this.options.cropZone.handleStyle.backgroundColor;
|
|
267
|
+
handle.style.border = this.options.cropZone.handleStyle.border;
|
|
268
|
+
handle.style.boxShadow = this.options.cropZone.handleStyle.boxShadow;
|
|
269
|
+
handle.style.boxSizing = "border-box";
|
|
270
|
+
handle.dataset.handle = handleType;
|
|
271
|
+
|
|
272
|
+
// Positionner la poignée
|
|
273
|
+
switch (handleType) {
|
|
274
|
+
case "tl": // Top-left
|
|
275
|
+
handle.style.top = "-7px";
|
|
276
|
+
handle.style.left = "-7px";
|
|
277
|
+
handle.style.cursor = "nwse-resize";
|
|
278
|
+
break;
|
|
279
|
+
case "tm": // Top-middle
|
|
280
|
+
handle.style.top = "-7px";
|
|
281
|
+
handle.style.left = "50%";
|
|
282
|
+
handle.style.marginLeft = "-7px";
|
|
283
|
+
handle.style.cursor = "ns-resize";
|
|
284
|
+
break;
|
|
285
|
+
case "tr": // Top-right
|
|
286
|
+
handle.style.top = "-7px";
|
|
287
|
+
handle.style.right = "-7px";
|
|
288
|
+
handle.style.cursor = "nesw-resize";
|
|
289
|
+
break;
|
|
290
|
+
case "ml": // Middle-left
|
|
291
|
+
handle.style.top = "50%";
|
|
292
|
+
handle.style.left = "-7px";
|
|
293
|
+
handle.style.marginTop = "-7px";
|
|
294
|
+
handle.style.cursor = "ew-resize";
|
|
295
|
+
break;
|
|
296
|
+
case "mr": // Middle-right
|
|
297
|
+
handle.style.top = "50%";
|
|
298
|
+
handle.style.right = "-7px";
|
|
299
|
+
handle.style.marginTop = "-7px";
|
|
300
|
+
handle.style.cursor = "ew-resize";
|
|
301
|
+
break;
|
|
302
|
+
case "bl": // Bottom-left
|
|
303
|
+
handle.style.bottom = "-7px";
|
|
304
|
+
handle.style.left = "-7px";
|
|
305
|
+
handle.style.cursor = "nesw-resize";
|
|
306
|
+
break;
|
|
307
|
+
case "bm": // Bottom-middle
|
|
308
|
+
handle.style.bottom = "-7px";
|
|
309
|
+
handle.style.left = "50%";
|
|
310
|
+
handle.style.marginLeft = "-7px";
|
|
311
|
+
handle.style.cursor = "ns-resize";
|
|
312
|
+
break;
|
|
313
|
+
case "br": // Bottom-right
|
|
314
|
+
handle.style.bottom = "-7px";
|
|
315
|
+
handle.style.right = "-7px";
|
|
316
|
+
handle.style.cursor = "nwse-resize";
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Ajouter l'écouteur d'événement
|
|
321
|
+
handle.addEventListener("mousedown", (e) =>
|
|
322
|
+
this._handleCropHandleMouseDown(e, handleType),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
overlay.appendChild(handle);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Ajouter l'écouteur pour le déplacement de l'overlay
|
|
329
|
+
overlay.addEventListener(
|
|
330
|
+
"mousedown",
|
|
331
|
+
this._handleCropOverlayMouseDown.bind(this),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
this.imageElement.parentNode.appendChild(overlay);
|
|
335
|
+
this.state.cropOverlay = overlay;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Gère l'événement mousedown sur une poignée de redimensionnement
|
|
340
|
+
* @private
|
|
341
|
+
* @param {MouseEvent} e - Événement mousedown
|
|
342
|
+
* @param {string} handleType - Type de poignée
|
|
343
|
+
*/
|
|
344
|
+
_handleCropHandleMouseDown(e, handleType) {
|
|
345
|
+
this.interaction.cropResizing = true;
|
|
346
|
+
this.interaction.activeHandle = handleType;
|
|
347
|
+
|
|
348
|
+
// Enregistrer les dimensions et position initiales
|
|
349
|
+
this.interaction.startX =
|
|
350
|
+
Number.parseInt(this.state.cropOverlay.style.left, 10) || 0;
|
|
351
|
+
this.interaction.startY =
|
|
352
|
+
Number.parseInt(this.state.cropOverlay.style.top, 10) || 0;
|
|
353
|
+
this.interaction.startWidth = this.state.cropOverlay.offsetWidth;
|
|
354
|
+
this.interaction.startHeight = this.state.cropOverlay.offsetHeight;
|
|
355
|
+
this.interaction.startMouseX = e.clientX;
|
|
356
|
+
this.interaction.startMouseY = e.clientY;
|
|
357
|
+
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Gère l'événement mousedown sur l'overlay de recadrage
|
|
364
|
+
* @private
|
|
365
|
+
* @param {MouseEvent} e - Événement mousedown
|
|
366
|
+
*/
|
|
367
|
+
_handleCropOverlayMouseDown(e) {
|
|
368
|
+
// Ignorer si on clique sur une poignée
|
|
369
|
+
if (e.target !== this.state.cropOverlay) return;
|
|
370
|
+
|
|
371
|
+
this.interaction.cropDragging = true;
|
|
372
|
+
this.state.cropOverlay.style.cursor = "grabbing";
|
|
373
|
+
|
|
374
|
+
// Enregistrer la position initiale
|
|
375
|
+
this.interaction.startX =
|
|
376
|
+
Number.parseInt(this.state.cropOverlay.style.left, 10) || 0;
|
|
377
|
+
this.interaction.startY =
|
|
378
|
+
Number.parseInt(this.state.cropOverlay.style.top, 10) || 0;
|
|
379
|
+
this.interaction.startMouseX = e.clientX;
|
|
380
|
+
this.interaction.startMouseY = e.clientY;
|
|
381
|
+
|
|
382
|
+
e.preventDefault();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Configure les écouteurs d'événements globaux
|
|
387
|
+
* @private
|
|
388
|
+
*/
|
|
389
|
+
_setupEventListeners() {
|
|
390
|
+
// Écouteur pour le redimensionnement de la fenêtre
|
|
391
|
+
window.addEventListener("resize", this._updateScaling.bind(this));
|
|
392
|
+
|
|
393
|
+
// Écouteur pour le chargement de l'image
|
|
394
|
+
if (!this.imageElement.complete) {
|
|
395
|
+
this.imageElement.addEventListener(
|
|
396
|
+
"load",
|
|
397
|
+
this._updateScaling.bind(this),
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Écouteurs pour les interactions de souris
|
|
402
|
+
document.addEventListener("mouseup", this._handleMouseUp.bind(this));
|
|
403
|
+
document.addEventListener("mousemove", this._handleMouseMove.bind(this));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Gère l'événement mouseup global
|
|
408
|
+
* @private
|
|
409
|
+
*/
|
|
410
|
+
_handleMouseUp() {
|
|
411
|
+
if (this.interaction.focusDragging) {
|
|
412
|
+
this.interaction.focusDragging = false;
|
|
413
|
+
if (this.state.focusMarker) this.state.focusMarker.style.cursor = "move";
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (this.interaction.cropDragging) {
|
|
417
|
+
this.interaction.cropDragging = false;
|
|
418
|
+
if (this.state.cropOverlay) this.state.cropOverlay.style.cursor = "move";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.interaction.cropResizing = false;
|
|
422
|
+
this.interaction.activeHandle = null;
|
|
423
|
+
document.body.style.cursor = "default";
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Gère l'événement mousemove global
|
|
428
|
+
* @private
|
|
429
|
+
* @param {MouseEvent} e - Événement mousemove
|
|
430
|
+
*/
|
|
431
|
+
_handleMouseMove(e) {
|
|
432
|
+
// Gestion du déplacement du point focal
|
|
433
|
+
if (this.interaction.focusDragging && this.state.focusMarker) {
|
|
434
|
+
this._handleFocusMarkerDrag(e);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Gestion du déplacement de la zone de recadrage
|
|
438
|
+
if (this.interaction.cropDragging) {
|
|
439
|
+
this._handleCropOverlayDrag(e);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Gestion du redimensionnement de la zone de recadrage
|
|
443
|
+
if (this.interaction.cropResizing) {
|
|
444
|
+
this._handleCropOverlayResize(e);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Gère le déplacement du marqueur de point focal
|
|
450
|
+
* @private
|
|
451
|
+
* @param {MouseEvent} e - Événement mousemove
|
|
452
|
+
*/
|
|
453
|
+
_handleFocusMarkerDrag(e) {
|
|
454
|
+
const imageRect = this.imageElement.getBoundingClientRect();
|
|
455
|
+
|
|
456
|
+
// Calculer la position cible relative à l'image
|
|
457
|
+
const targetScaledX =
|
|
458
|
+
e.clientX - imageRect.left - this.interaction.focusDragOffsetX;
|
|
459
|
+
const targetScaledY =
|
|
460
|
+
e.clientY - imageRect.top - this.interaction.focusDragOffsetY;
|
|
461
|
+
|
|
462
|
+
// Convertir en coordonnées originales
|
|
463
|
+
const original = this._toOriginalCoords(targetScaledX, targetScaledY);
|
|
464
|
+
|
|
465
|
+
// Mettre à jour le point focal
|
|
466
|
+
this.setFocusPoint(original.x, original.y);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Gère le déplacement de l'overlay de recadrage
|
|
471
|
+
* @private
|
|
472
|
+
* @param {MouseEvent} e - Événement mousemove
|
|
473
|
+
*/
|
|
474
|
+
_handleCropOverlayDrag(e) {
|
|
475
|
+
const deltaX = e.clientX - this.interaction.startMouseX;
|
|
476
|
+
const deltaY = e.clientY - this.interaction.startMouseY;
|
|
477
|
+
|
|
478
|
+
// Calculer la nouvelle position en pixels d'affichage
|
|
479
|
+
const newX = this.interaction.startX + deltaX;
|
|
480
|
+
const newY = this.interaction.startY + deltaY;
|
|
481
|
+
|
|
482
|
+
// Convertir en coordonnées originales
|
|
483
|
+
const original = this._toOriginalCoords(newX, newY);
|
|
484
|
+
|
|
485
|
+
// Obtenir les dimensions actuelles en coordonnées originales
|
|
486
|
+
const { width, height } = this.state.cropZone;
|
|
487
|
+
|
|
488
|
+
// Mettre à jour la zone de recadrage
|
|
489
|
+
this.setCropZone(original.x, original.y, width, height);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Gère le redimensionnement de l'overlay de recadrage
|
|
494
|
+
* @private
|
|
495
|
+
* @param {MouseEvent} e - Événement mousemove
|
|
496
|
+
*/
|
|
497
|
+
_handleCropOverlayResize(e) {
|
|
498
|
+
if (!this.interaction.activeHandle) return;
|
|
499
|
+
|
|
500
|
+
const deltaX = e.clientX - this.interaction.startMouseX;
|
|
501
|
+
const deltaY = e.clientY - this.interaction.startMouseY;
|
|
502
|
+
|
|
503
|
+
let newX = this.interaction.startX;
|
|
504
|
+
let newY = this.interaction.startY;
|
|
505
|
+
let newWidth = this.interaction.startWidth;
|
|
506
|
+
let newHeight = this.interaction.startHeight;
|
|
507
|
+
|
|
508
|
+
// Ajuster en fonction de la poignée active
|
|
509
|
+
if (this.interaction.activeHandle.includes("t")) {
|
|
510
|
+
// Top
|
|
511
|
+
newY = this.interaction.startY + deltaY;
|
|
512
|
+
newHeight = this.interaction.startHeight - deltaY;
|
|
513
|
+
}
|
|
514
|
+
if (this.interaction.activeHandle.includes("b")) {
|
|
515
|
+
// Bottom
|
|
516
|
+
newHeight = this.interaction.startHeight + deltaY;
|
|
517
|
+
}
|
|
518
|
+
if (this.interaction.activeHandle.includes("l")) {
|
|
519
|
+
// Left
|
|
520
|
+
newX = this.interaction.startX + deltaX;
|
|
521
|
+
newWidth = this.interaction.startWidth - deltaX;
|
|
522
|
+
}
|
|
523
|
+
if (this.interaction.activeHandle.includes("r")) {
|
|
524
|
+
// Right
|
|
525
|
+
newWidth = this.interaction.startWidth + deltaX;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Empêcher les dimensions négatives
|
|
529
|
+
if (newWidth < 10) {
|
|
530
|
+
if (this.interaction.activeHandle.includes("l")) {
|
|
531
|
+
newX = this.interaction.startX + this.interaction.startWidth - 10;
|
|
532
|
+
}
|
|
533
|
+
newWidth = 10;
|
|
534
|
+
}
|
|
535
|
+
if (newHeight < 10) {
|
|
536
|
+
if (this.interaction.activeHandle.includes("t")) {
|
|
537
|
+
newY = this.interaction.startY + this.interaction.startHeight - 10;
|
|
538
|
+
}
|
|
539
|
+
newHeight = 10;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Convertir les coordonnées d'affichage en coordonnées originales
|
|
543
|
+
const originalX = newX / this.state.scaleX;
|
|
544
|
+
const originalY = newY / this.state.scaleY;
|
|
545
|
+
const originalWidth = newWidth / this.state.scaleX;
|
|
546
|
+
const originalHeight = newHeight / this.state.scaleY;
|
|
547
|
+
|
|
548
|
+
// Mettre à jour la zone de recadrage
|
|
549
|
+
this.setCropZone(originalX, originalY, originalWidth, originalHeight);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Met à jour la position du marqueur de point focal
|
|
554
|
+
* @private
|
|
555
|
+
*/
|
|
556
|
+
_updateFocusMarkerPosition() {
|
|
557
|
+
if (!this.state.focusMarker) return;
|
|
558
|
+
|
|
559
|
+
const { x, y } = this.state.focusPoint;
|
|
560
|
+
|
|
561
|
+
// Limiter les coordonnées aux dimensions de l'image
|
|
562
|
+
const clampedX = Math.max(0, Math.min(x, this.state.originalWidth));
|
|
563
|
+
const clampedY = Math.max(0, Math.min(y, this.state.originalHeight));
|
|
564
|
+
|
|
565
|
+
// Mettre à jour l'état si les coordonnées ont été limitées
|
|
566
|
+
if (x !== clampedX || y !== clampedY) {
|
|
567
|
+
this.state.focusPoint = { x: clampedX, y: clampedY };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const scaled = this._toScaledCoords(clampedX, clampedY);
|
|
571
|
+
|
|
572
|
+
// LOGGING: Validate padding offset
|
|
573
|
+
if (this.options.debug) {
|
|
574
|
+
const container = this.imageElement.parentNode;
|
|
575
|
+
const computedStyle = window.getComputedStyle(container);
|
|
576
|
+
const paddingLeft = Number.parseFloat(computedStyle.paddingLeft);
|
|
577
|
+
const paddingTop = Number.parseFloat(computedStyle.paddingTop);
|
|
578
|
+
console.log(
|
|
579
|
+
"[FocusMarker] scaled:",
|
|
580
|
+
scaled,
|
|
581
|
+
"paddingLeft:",
|
|
582
|
+
paddingLeft,
|
|
583
|
+
"paddingTop:",
|
|
584
|
+
paddingTop,
|
|
585
|
+
"containerRect:",
|
|
586
|
+
container.getBoundingClientRect(),
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Ajuster pour centrer le marqueur
|
|
591
|
+
// Adjust for container padding (use unique variable names)
|
|
592
|
+
let focusPaddingLeft = 0;
|
|
593
|
+
let focusPaddingTop = 0;
|
|
594
|
+
{
|
|
595
|
+
const container = this.imageElement.parentNode;
|
|
596
|
+
const computedStyle = window.getComputedStyle(container);
|
|
597
|
+
focusPaddingLeft = Number.parseFloat(computedStyle.paddingLeft);
|
|
598
|
+
focusPaddingTop = Number.parseFloat(computedStyle.paddingTop);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
this.state.focusMarker.style.left = `${
|
|
602
|
+
scaled.x + focusPaddingLeft - this.state.focusMarker.offsetWidth / 2
|
|
603
|
+
}px`;
|
|
604
|
+
this.state.focusMarker.style.top = `${
|
|
605
|
+
scaled.y + focusPaddingTop - this.state.focusMarker.offsetHeight / 2
|
|
606
|
+
}px`;
|
|
607
|
+
if (this.options.debug) {
|
|
608
|
+
console.log(
|
|
609
|
+
"[FocusMarker] style.left:",
|
|
610
|
+
this.state.focusMarker.style.left,
|
|
611
|
+
"style.top:",
|
|
612
|
+
this.state.focusMarker.style.top,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Met à jour la position et les dimensions de l'overlay de recadrage
|
|
619
|
+
* @private
|
|
620
|
+
*/
|
|
621
|
+
_updateCropOverlayPosition() {
|
|
622
|
+
if (!this.state.cropOverlay) return;
|
|
623
|
+
|
|
624
|
+
const { x, y, width, height } = this.state.cropZone;
|
|
625
|
+
|
|
626
|
+
// Limiter aux dimensions de l'image
|
|
627
|
+
const clampedX = Math.max(0, Math.min(x, this.state.originalWidth - width));
|
|
628
|
+
const clampedY = Math.max(
|
|
629
|
+
0,
|
|
630
|
+
Math.min(y, this.state.originalHeight - height),
|
|
631
|
+
);
|
|
632
|
+
const clampedWidth = Math.max(
|
|
633
|
+
10,
|
|
634
|
+
Math.min(width, this.state.originalWidth - clampedX),
|
|
635
|
+
);
|
|
636
|
+
const clampedHeight = Math.max(
|
|
637
|
+
10,
|
|
638
|
+
Math.min(height, this.state.originalHeight - clampedY),
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
// Mettre à jour l'état si les valeurs ont été limitées
|
|
642
|
+
if (
|
|
643
|
+
x !== clampedX ||
|
|
644
|
+
y !== clampedY ||
|
|
645
|
+
width !== clampedWidth ||
|
|
646
|
+
height !== clampedHeight
|
|
647
|
+
) {
|
|
648
|
+
this.state.cropZone = {
|
|
649
|
+
x: clampedX,
|
|
650
|
+
y: clampedY,
|
|
651
|
+
width: clampedWidth,
|
|
652
|
+
height: clampedHeight,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Convertir en coordonnées d'affichage
|
|
657
|
+
const scaled = this._toScaledCoords(clampedX, clampedY);
|
|
658
|
+
const scaledWidth = clampedWidth * this.state.scaleX;
|
|
659
|
+
const scaledHeight = clampedHeight * this.state.scaleY;
|
|
660
|
+
|
|
661
|
+
// LOGGING: Validate padding offset
|
|
662
|
+
if (this.options.debug) {
|
|
663
|
+
const container = this.imageElement.parentNode;
|
|
664
|
+
const computedStyle = window.getComputedStyle(container);
|
|
665
|
+
const paddingLeft = Number.parseFloat(computedStyle.paddingLeft);
|
|
666
|
+
const paddingTop = Number.parseFloat(computedStyle.paddingTop);
|
|
667
|
+
console.log(
|
|
668
|
+
"[CropOverlay] scaled:",
|
|
669
|
+
scaled,
|
|
670
|
+
"paddingLeft:",
|
|
671
|
+
paddingLeft,
|
|
672
|
+
"paddingTop:",
|
|
673
|
+
paddingTop,
|
|
674
|
+
"containerRect:",
|
|
675
|
+
container.getBoundingClientRect(),
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Mettre à jour l'overlay
|
|
680
|
+
// Adjust for container padding (use unique variable names)
|
|
681
|
+
let cropPaddingLeft = 0;
|
|
682
|
+
let cropPaddingTop = 0;
|
|
683
|
+
{
|
|
684
|
+
const container = this.imageElement.parentNode;
|
|
685
|
+
const computedStyle = window.getComputedStyle(container);
|
|
686
|
+
cropPaddingLeft = Number.parseFloat(computedStyle.paddingLeft);
|
|
687
|
+
cropPaddingTop = Number.parseFloat(computedStyle.paddingTop);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
this.state.cropOverlay.style.left = `${scaled.x + cropPaddingLeft}px`;
|
|
691
|
+
this.state.cropOverlay.style.top = `${scaled.y + cropPaddingTop}px`;
|
|
692
|
+
this.state.cropOverlay.style.width = `${scaledWidth}px`;
|
|
693
|
+
this.state.cropOverlay.style.height = `${scaledHeight}px`;
|
|
694
|
+
if (this.options.debug) {
|
|
695
|
+
console.log(
|
|
696
|
+
"[CropOverlay] style.left:",
|
|
697
|
+
this.state.cropOverlay.style.left,
|
|
698
|
+
"style.top:",
|
|
699
|
+
this.state.cropOverlay.style.top,
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Active ou désactive le point focal
|
|
706
|
+
* @public
|
|
707
|
+
* @param {boolean} active - État d'activation
|
|
708
|
+
* @returns {ImageTool} Instance pour chaînage
|
|
709
|
+
*/
|
|
710
|
+
toggleFocusPoint(active) {
|
|
711
|
+
if (!this.options.focusPoint.enabled) return this;
|
|
712
|
+
|
|
713
|
+
active === undefined ? !this.state.focusActive : active;
|
|
714
|
+
|
|
715
|
+
if (active && !this.state.focusActive) {
|
|
716
|
+
// Activer le point focal
|
|
717
|
+
if (!this.state.focusMarker) {
|
|
718
|
+
this._createFocusMarker();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Positionner au centre de l'image par défaut si pas déjà défini
|
|
722
|
+
if (this.state.focusPoint.x === 0 && this.state.focusPoint.y === 0) {
|
|
723
|
+
this.state.focusPoint = {
|
|
724
|
+
x: this.state.originalWidth / 2,
|
|
725
|
+
y: this.state.originalHeight / 2,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
this._updateFocusMarkerPosition();
|
|
730
|
+
this.state.focusMarker.style.display = "block";
|
|
731
|
+
this.state.focusActive = true;
|
|
732
|
+
} else if (!active && this.state.focusActive) {
|
|
733
|
+
// Désactiver le point focal
|
|
734
|
+
if (this.state.focusMarker) {
|
|
735
|
+
this.state.focusMarker.style.display = "none";
|
|
736
|
+
}
|
|
737
|
+
this.state.focusActive = false;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Notifier le changement
|
|
741
|
+
this._notifyChange();
|
|
742
|
+
|
|
743
|
+
return this;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Active ou désactive la zone de recadrage
|
|
748
|
+
* @public
|
|
749
|
+
* @param {boolean} active - État d'activation
|
|
750
|
+
* @returns {ImageTool} Instance pour chaînage
|
|
751
|
+
*/
|
|
752
|
+
toggleCropZone(active) {
|
|
753
|
+
if (!this.options.cropZone.enabled) return this;
|
|
754
|
+
|
|
755
|
+
active === undefined ? !this.state.cropActive : active;
|
|
756
|
+
|
|
757
|
+
if (active && !this.state.cropActive) {
|
|
758
|
+
// Activer la zone de recadrage
|
|
759
|
+
if (!this.state.cropOverlay) {
|
|
760
|
+
this._createCropOverlay();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Définir une zone par défaut si pas déjà définie
|
|
764
|
+
if (this.state.cropZone.width === 0 || this.state.cropZone.height === 0) {
|
|
765
|
+
const defaultWidth = this.state.originalWidth / 2;
|
|
766
|
+
const defaultHeight = this.state.originalHeight / 2;
|
|
767
|
+
const defaultX = (this.state.originalWidth - defaultWidth) / 2;
|
|
768
|
+
const defaultY = (this.state.originalHeight - defaultHeight) / 2;
|
|
769
|
+
|
|
770
|
+
this.state.cropZone = {
|
|
771
|
+
x: defaultX,
|
|
772
|
+
y: defaultY,
|
|
773
|
+
width: defaultWidth,
|
|
774
|
+
height: defaultHeight,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
this._updateCropOverlayPosition();
|
|
779
|
+
this.state.cropOverlay.style.display = "block";
|
|
780
|
+
this.state.cropActive = true;
|
|
781
|
+
} else if (!active && this.state.cropActive) {
|
|
782
|
+
// Désactiver la zone de recadrage
|
|
783
|
+
if (this.state.cropOverlay) {
|
|
784
|
+
this.state.cropOverlay.style.display = "none";
|
|
785
|
+
}
|
|
786
|
+
this.state.cropActive = false;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Notifier le changement
|
|
790
|
+
this._notifyChange();
|
|
791
|
+
|
|
792
|
+
return this;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Définit la position du point focal
|
|
797
|
+
* @public
|
|
798
|
+
* @param {number} x - Coordonnée X en pixels originaux
|
|
799
|
+
* @param {number} y - Coordonnée Y en pixels originaux
|
|
800
|
+
* @returns {ImageTool} Instance pour chaînage
|
|
801
|
+
*/
|
|
802
|
+
setFocusPoint(x, y) {
|
|
803
|
+
// Limiter les coordonnées aux dimensions de l'image
|
|
804
|
+
const clampedX = Math.max(0, Math.min(x, this.state.originalWidth));
|
|
805
|
+
const clampedY = Math.max(0, Math.min(y, this.state.originalHeight));
|
|
806
|
+
|
|
807
|
+
this.state.focusPoint = { x: clampedX, y: clampedY };
|
|
808
|
+
|
|
809
|
+
if (this.state.focusActive && this.state.focusMarker) {
|
|
810
|
+
this._updateFocusMarkerPosition();
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Notifier le changement
|
|
814
|
+
this._notifyChange();
|
|
815
|
+
|
|
816
|
+
return this;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Définit la position et les dimensions de la zone de recadrage
|
|
821
|
+
* @public
|
|
822
|
+
* @param {number} x - Coordonnée X en pixels originaux
|
|
823
|
+
* @param {number} y - Coordonnée Y en pixels originaux
|
|
824
|
+
* @param {number} width - Largeur en pixels originaux
|
|
825
|
+
* @param {number} height - Hauteur en pixels originaux
|
|
826
|
+
* @returns {ImageTool} Instance pour chaînage
|
|
827
|
+
*/
|
|
828
|
+
setCropZone(x, y, width, height) {
|
|
829
|
+
// Limiter aux dimensions de l'image
|
|
830
|
+
const clampedX = Math.max(0, Math.min(x, this.state.originalWidth - width));
|
|
831
|
+
const clampedY = Math.max(
|
|
832
|
+
0,
|
|
833
|
+
Math.min(y, this.state.originalHeight - height),
|
|
834
|
+
);
|
|
835
|
+
const clampedWidth = Math.max(
|
|
836
|
+
10,
|
|
837
|
+
Math.min(width, this.state.originalWidth - clampedX),
|
|
838
|
+
);
|
|
839
|
+
const clampedHeight = Math.max(
|
|
840
|
+
10,
|
|
841
|
+
Math.min(height, this.state.originalHeight - clampedY),
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
this.state.cropZone = {
|
|
845
|
+
x: clampedX,
|
|
846
|
+
y: clampedY,
|
|
847
|
+
width: clampedWidth,
|
|
848
|
+
height: clampedHeight,
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
if (this.state.cropActive && this.state.cropOverlay) {
|
|
852
|
+
this._updateCropOverlayPosition();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Notifier le changement
|
|
856
|
+
this._notifyChange();
|
|
857
|
+
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Obtient la position actuelle du point focal
|
|
863
|
+
* @public
|
|
864
|
+
* @returns {Object} Coordonnées du point focal {x, y}
|
|
865
|
+
*/
|
|
866
|
+
getFocusPoint() {
|
|
867
|
+
return { ...this.state.focusPoint };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Obtient la position et les dimensions actuelles de la zone de recadrage
|
|
872
|
+
* @public
|
|
873
|
+
* @returns {Object} Zone de recadrage {x, y, width, height}
|
|
874
|
+
*/
|
|
875
|
+
getCropZone() {
|
|
876
|
+
return { ...this.state.cropZone };
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Obtient les dimensions originales de l'image
|
|
881
|
+
* @public
|
|
882
|
+
* @returns {Object} Dimensions {width, height}
|
|
883
|
+
*/
|
|
884
|
+
getImageDimensions() {
|
|
885
|
+
return {
|
|
886
|
+
width: this.state.originalWidth,
|
|
887
|
+
height: this.state.originalHeight,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Notifie les changements via le callback
|
|
893
|
+
* @private
|
|
894
|
+
*/
|
|
895
|
+
_notifyChange() {
|
|
896
|
+
if (typeof this.options.onChange === "function") {
|
|
897
|
+
this.options.onChange({
|
|
898
|
+
focusPoint: this.getFocusPoint(),
|
|
899
|
+
cropZone: this.getCropZone(),
|
|
900
|
+
focusActive: this.state.focusActive,
|
|
901
|
+
cropActive: this.state.cropActive,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Détruit l'instance et nettoie les ressources
|
|
908
|
+
* @public
|
|
909
|
+
*/
|
|
910
|
+
destroy() {
|
|
911
|
+
// Supprimer les éléments DOM
|
|
912
|
+
if (this.state.focusMarker?.parentNode) {
|
|
913
|
+
this.state.focusMarker.parentNode.removeChild(this.state.focusMarker);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (this.state.cropOverlay?.parentNode) {
|
|
917
|
+
this.state.cropOverlay.parentNode.removeChild(this.state.cropOverlay);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Supprimer les écouteurs d'événements
|
|
921
|
+
window.removeEventListener("resize", this._updateScaling.bind(this));
|
|
922
|
+
document.removeEventListener("mouseup", this._handleMouseUp.bind(this));
|
|
923
|
+
document.removeEventListener("mousemove", this._handleMouseMove.bind(this));
|
|
924
|
+
|
|
925
|
+
// Réinitialiser l'état
|
|
926
|
+
this.state = null;
|
|
927
|
+
this.interaction = null;
|
|
928
|
+
this.options = null;
|
|
929
|
+
this.imageElement = null;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
847
932
|
|
|
848
933
|
/**
|
|
849
934
|
* Point d'entrée principal pour le package image-tool
|