viewerjs 1.12.0 → 1.13.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.
@@ -14,10 +14,16 @@ export default {
14
14
 
15
15
  /**
16
16
  * Show the navbar.
17
- * @type {boolean | number}
17
+ * @type {boolean | number | string | Object}
18
18
  */
19
19
  navbar: true,
20
20
 
21
+ /**
22
+ * Show the navigation buttons.
23
+ * @type {boolean | number | Object}
24
+ */
25
+ navigation: false,
26
+
21
27
  /**
22
28
  * Specify the visibility and the content of the title.
23
29
  * @type {boolean | number | Function | Array}
@@ -88,6 +94,12 @@ export default {
88
94
  */
89
95
  inline: false,
90
96
 
97
+ /**
98
+ * Enable to automatically play the images when playing.
99
+ * @type {boolean}
100
+ */
101
+ autoplay: true,
102
+
91
103
  /**
92
104
  * The amount of time to delay between automatically cycling an image when playing.
93
105
  * @type {number}
@@ -118,6 +130,12 @@ export default {
118
130
  */
119
131
  loop: true,
120
132
 
133
+ /**
134
+ * Enable to preload the next or previous image before viewing it.
135
+ * @type {boolean}
136
+ */
137
+ preload: true,
138
+
121
139
  /**
122
140
  * Min width of the viewer in inline mode.
123
141
  * @type {number}
@@ -136,12 +154,30 @@ export default {
136
154
  */
137
155
  movable: true,
138
156
 
157
+ /**
158
+ * Show a magnifier when hovering over the image in fullscreen mode.
159
+ * @type {boolean | Object}
160
+ */
161
+ magnifier: false,
162
+
139
163
  /**
140
164
  * Enable to rotate the image.
141
165
  * @type {boolean}
142
166
  */
143
167
  rotatable: true,
144
168
 
169
+ /**
170
+ * Enable to rotate the current image by gesture.
171
+ * @type {boolean}
172
+ */
173
+ rotateOnGesture: true,
174
+
175
+ /**
176
+ * Enable to rotate the current image by dragging on the touch screen.
177
+ * @type {boolean}
178
+ */
179
+ rotateOnTouch: true,
180
+
145
181
  /**
146
182
  * Enable to scale the image.
147
183
  * @type {boolean}
@@ -160,6 +196,12 @@ export default {
160
196
  */
161
197
  zoomOnTouch: true,
162
198
 
199
+ /**
200
+ * Enable to zoom the current image by gesture.
201
+ * @type {boolean}
202
+ */
203
+ zoomOnGesture: true,
204
+
163
205
  /**
164
206
  * Enable to zoom the image by wheeling mouse.
165
207
  * @type {boolean}
@@ -187,7 +229,7 @@ export default {
187
229
 
188
230
  /**
189
231
  * Enable CSS3 Transition for some special elements.
190
- * @type {boolean}
232
+ * @type {boolean | Object}
191
233
  */
192
234
  transition: true,
193
235
 
@@ -211,13 +253,13 @@ export default {
211
253
 
212
254
  /**
213
255
  * Define the min ratio of the image when zoom out.
214
- * @type {number}
256
+ * @type {number | Function}
215
257
  */
216
258
  minZoomRatio: 0.01,
217
259
 
218
260
  /**
219
261
  * Define the max ratio of the image when zoom in.
220
- * @type {number}
262
+ * @type {number | Function}
221
263
  */
222
264
  maxZoomRatio: 100,
223
265
 
package/src/js/events.js CHANGED
@@ -2,8 +2,11 @@ import {
2
2
  EVENT_CLICK,
3
3
  EVENT_DBLCLICK,
4
4
  EVENT_DRAG_START,
5
+ EVENT_GESTURE,
5
6
  EVENT_KEY_DOWN,
6
7
  EVENT_POINTER_DOWN,
8
+ EVENT_POINTER_ENTER,
9
+ EVENT_POINTER_LEAVE,
7
10
  EVENT_POINTER_MOVE,
8
11
  EVENT_POINTER_UP,
9
12
  EVENT_RESIZE,
@@ -17,10 +20,17 @@ import {
17
20
  export default {
18
21
  bind() {
19
22
  const { options, viewer, canvas } = this;
20
- const document = this.element.ownerDocument;
23
+ const { ownerDocument: document } = this;
21
24
 
22
25
  addListener(viewer, EVENT_CLICK, (this.onClick = this.click.bind(this)));
23
26
  addListener(viewer, EVENT_DRAG_START, (this.onDragStart = this.dragstart.bind(this)));
27
+ addListener(viewer, EVENT_POINTER_ENTER, (this.onMagnifyEnter = this.magnify.bind(this)));
28
+ addListener(viewer, EVENT_POINTER_MOVE, (this.onMagnify = this.magnify.bind(this)));
29
+ addListener(
30
+ viewer,
31
+ EVENT_POINTER_LEAVE,
32
+ (this.onMagnifierLeave = this.hideMagnifier.bind(this)),
33
+ );
24
34
  addListener(canvas, EVENT_POINTER_DOWN, (this.onPointerDown = this.pointerdown.bind(this)));
25
35
  addListener(document, EVENT_POINTER_MOVE, (this.onPointerMove = this.pointermove.bind(this)));
26
36
  addListener(document, EVENT_POINTER_UP, (this.onPointerUp = this.pointerup.bind(this)));
@@ -34,6 +44,13 @@ export default {
34
44
  });
35
45
  }
36
46
 
47
+ if ((options.zoomable && options.zoomOnGesture)
48
+ || (options.rotatable && options.rotateOnGesture)) {
49
+ addListener(viewer, EVENT_GESTURE, (this.onGesture = this.gesture.bind(this)), {
50
+ passive: false,
51
+ });
52
+ }
53
+
37
54
  if (options.toggleOnDblclick) {
38
55
  addListener(canvas, EVENT_DBLCLICK, (this.onDblclick = this.dblclick.bind(this)));
39
56
  }
@@ -41,10 +58,13 @@ export default {
41
58
 
42
59
  unbind() {
43
60
  const { options, viewer, canvas } = this;
44
- const document = this.element.ownerDocument;
61
+ const { ownerDocument: document } = this;
45
62
 
46
63
  removeListener(viewer, EVENT_CLICK, this.onClick);
47
64
  removeListener(viewer, EVENT_DRAG_START, this.onDragStart);
65
+ removeListener(viewer, EVENT_POINTER_ENTER, this.onMagnifyEnter);
66
+ removeListener(viewer, EVENT_POINTER_MOVE, this.onMagnify);
67
+ removeListener(viewer, EVENT_POINTER_LEAVE, this.onMagnifierLeave);
48
68
  removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);
49
69
  removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);
50
70
  removeListener(document, EVENT_POINTER_UP, this.onPointerUp);
@@ -58,6 +78,13 @@ export default {
58
78
  });
59
79
  }
60
80
 
81
+ if ((options.zoomable && options.zoomOnGesture)
82
+ || (options.rotatable && options.rotateOnGesture)) {
83
+ removeListener(viewer, EVENT_GESTURE, this.onGesture, {
84
+ passive: false,
85
+ });
86
+ }
87
+
61
88
  if (options.toggleOnDblclick) {
62
89
  removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);
63
90
  }
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ACTION_MOVE,
3
+ ACTION_ROTATE,
3
4
  ACTION_SWITCH,
5
+ ACTION_TRANSFORM,
4
6
  ACTION_ZOOM,
5
7
  CLASS_INVISIBLE,
6
8
  CLASS_LOADING,
@@ -25,6 +27,9 @@ import {
25
27
  getTransforms,
26
28
  isFunction,
27
29
  isNumber,
30
+ isPlainObject,
31
+ isTransitionEnabled,
32
+ isUndefined,
28
33
  removeClass,
29
34
  setStyle,
30
35
  toggleClass,
@@ -168,11 +173,18 @@ export default {
168
173
 
169
174
  this.initImage(() => {
170
175
  toggleClass(image, CLASS_MOVE, options.movable);
171
- toggleClass(image, CLASS_TRANSITION, options.transition);
176
+ toggleClass(
177
+ image,
178
+ CLASS_TRANSITION,
179
+ isTransitionEnabled(options, 'view'),
180
+ );
172
181
 
173
182
  this.renderImage(() => {
174
183
  this.viewed = true;
175
184
  this.viewing = false;
185
+ setTimeout(() => {
186
+ toggleClass(image, CLASS_TRANSITION, options.transition);
187
+ }, 300);
176
188
 
177
189
  if (isFunction(options.viewed)) {
178
190
  addListener(element, EVENT_VIEWED, options.viewed, {
@@ -333,6 +345,105 @@ export default {
333
345
  }
334
346
  },
335
347
 
348
+ magnify(event) {
349
+ if (event.changedTouches || (event.pointerType && event.pointerType !== 'mouse')) {
350
+ this.hideMagnifier();
351
+ return;
352
+ }
353
+
354
+ this.magnifierPoint = {
355
+ clientX: event.clientX,
356
+ clientY: event.clientY,
357
+ };
358
+ this.renderMagnifier();
359
+ },
360
+
361
+ renderMagnifier() {
362
+ const {
363
+ options,
364
+ imageData,
365
+ magnifier,
366
+ magnifierImage,
367
+ viewer,
368
+ } = this;
369
+ const config = isPlainObject(options.magnifier) ? options.magnifier : {};
370
+ const point = this.magnifierPoint;
371
+
372
+ if (!options.magnifier || !this.fulled || !this.viewed || !magnifier
373
+ || !magnifierImage || !point) {
374
+ this.hideMagnifier();
375
+ return;
376
+ }
377
+
378
+ const size = Math.max(1, Number(config.size) || 100);
379
+ const zoomRatio = Math.max(1, Number(config.zoomRatio) || 2);
380
+ const opacity = Number(config.opacity);
381
+ const rect = viewer.getBoundingClientRect();
382
+ const x = point.clientX - rect.left;
383
+ const y = point.clientY - rect.top;
384
+ const imageURL = this.image.currentSrc || this.image.src;
385
+ const imageRect = this.image.getBoundingClientRect();
386
+
387
+ if (point.clientX < imageRect.left || point.clientX > imageRect.right
388
+ || point.clientY < imageRect.top || point.clientY > imageRect.bottom) {
389
+ this.hideMagnifier();
390
+ return;
391
+ }
392
+
393
+ const scaleX = isNumber(imageData.scaleX) ? imageData.scaleX : 1;
394
+ const scaleY = isNumber(imageData.scaleY) ? imageData.scaleY : 1;
395
+ const rotate = ((imageData.rotate || 0) * Math.PI) / 180;
396
+ const cos = Math.cos(rotate);
397
+ const sin = Math.sin(rotate);
398
+ const matrixA = cos * scaleX;
399
+ const matrixB = sin * scaleX;
400
+ const matrixC = -sin * scaleY;
401
+ const matrixD = cos * scaleY;
402
+ const determinant = scaleX * scaleY;
403
+
404
+ if (determinant === 0) {
405
+ this.hideMagnifier();
406
+ return;
407
+ }
408
+
409
+ const centerX = imageData.x + (imageData.width / 2);
410
+ const centerY = imageData.y + (imageData.height / 2);
411
+ const localX = (matrixD * (x - centerX) - matrixC * (y - centerY)) / determinant;
412
+ const localY = (-matrixB * (x - centerX) + matrixA * (y - centerY)) / determinant;
413
+ const sourceX = localX + imageData.width / 2;
414
+ const sourceY = localY + imageData.height / 2;
415
+ const magnifiedWidth = imageData.width * zoomRatio;
416
+ const magnifiedHeight = imageData.height * zoomRatio;
417
+ const transformedX = matrixA * (sourceX * zoomRatio - magnifiedWidth / 2)
418
+ + matrixC * (sourceY * zoomRatio - magnifiedHeight / 2);
419
+ const transformedY = matrixB * (sourceX * zoomRatio - magnifiedWidth / 2)
420
+ + matrixD * (sourceY * zoomRatio - magnifiedHeight / 2);
421
+
422
+ this.magnifierSourcePoint = {
423
+ x: sourceX,
424
+ y: sourceY,
425
+ };
426
+ magnifier.style.width = `${size}px`;
427
+ magnifier.style.height = `${size}px`;
428
+ magnifier.style.opacity = `${Math.max(0, Math.min(1, isNumber(opacity) ? opacity : 1))}`;
429
+ magnifierImage.src = imageURL;
430
+ magnifierImage.style.width = `${magnifiedWidth}px`;
431
+ magnifierImage.style.height = `${magnifiedHeight}px`;
432
+ magnifierImage.style.left = `${size / 2 - magnifiedWidth / 2 - transformedX}px`;
433
+ magnifierImage.style.top = `${size / 2 - magnifiedHeight / 2 - transformedY}px`;
434
+ magnifierImage.style.transform = `rotate(${imageData.rotate || 0}deg) scaleX(${scaleX}) scaleY(${scaleY})`;
435
+ magnifier.removeAttribute('aria-hidden');
436
+ addClass(magnifier, 'viewer-show');
437
+ },
438
+
439
+ hideMagnifier() {
440
+ if (this.magnifier) {
441
+ removeClass(this.magnifier, 'viewer-show');
442
+ this.magnifier.setAttribute('aria-hidden', true);
443
+ this.magnifierPoint = null;
444
+ }
445
+ },
446
+
336
447
  pointerdown(event) {
337
448
  const { options, pointers } = this;
338
449
  const { buttons, button } = event;
@@ -374,13 +485,21 @@ export default {
374
485
 
375
486
  let action = options.movable ? ACTION_MOVE : false;
376
487
 
377
- if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {
378
- action = ACTION_ZOOM;
488
+ if ((
489
+ (options.zoomable && options.zoomOnTouch)
490
+ || (options.rotatable && options.rotateOnTouch)
491
+ ) && Object.keys(pointers).length > 1) {
492
+ // action = ACTION_ZOOM;
493
+ // action = ACTION_ROTATE;
494
+ action = ACTION_TRANSFORM;
379
495
  } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {
380
496
  action = ACTION_SWITCH;
381
497
  }
382
498
 
383
- if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
499
+ if (action === ACTION_MOVE
500
+ || action === ACTION_ZOOM
501
+ || action === ACTION_ROTATE
502
+ || action === ACTION_TRANSFORM) {
384
503
  removeClass(this.image, CLASS_TRANSITION);
385
504
  }
386
505
 
@@ -427,8 +546,16 @@ export default {
427
546
 
428
547
  event.preventDefault();
429
548
 
430
- if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
431
- addClass(this.image, CLASS_TRANSITION);
549
+ if (action === ACTION_MOVE
550
+ || action === ACTION_ZOOM
551
+ || action === ACTION_ROTATE
552
+ || action === ACTION_TRANSFORM) {
553
+ const transition = action === ACTION_TRANSFORM
554
+ ? isTransitionEnabled(options, ACTION_ZOOM)
555
+ || isTransitionEnabled(options, ACTION_ROTATE)
556
+ : isTransitionEnabled(options, action);
557
+
558
+ toggleClass(this.image, CLASS_TRANSITION, transition);
432
559
  }
433
560
 
434
561
  this.action = false;
@@ -437,6 +564,7 @@ export default {
437
564
  if (
438
565
  IS_TOUCH_DEVICE
439
566
  && action !== ACTION_ZOOM
567
+ && action !== ACTION_TRANSFORM
440
568
  && pointer
441
569
  && (Date.now() - pointer.timeStamp < 500)
442
570
  ) {
@@ -492,6 +620,13 @@ export default {
492
620
  this.initContainer();
493
621
  this.initViewer();
494
622
  this.renderViewer();
623
+
624
+ const navbarOptions = isPlainObject(this.options.navbar) ? this.options.navbar : {};
625
+
626
+ if (isUndefined(navbarOptions.visibleItemCount)) {
627
+ this.initList(this.index);
628
+ }
629
+
495
630
  this.renderList();
496
631
 
497
632
  if (this.viewed) {
@@ -527,6 +662,10 @@ export default {
527
662
 
528
663
  event.preventDefault();
529
664
 
665
+ if (this.gesturing) {
666
+ return;
667
+ }
668
+
530
669
  // Limit wheel speed to prevent zoom too fast
531
670
  if (this.wheeling) {
532
671
  return;
@@ -551,4 +690,53 @@ export default {
551
690
 
552
691
  this.zoom(-delta * ratio, true, null, event);
553
692
  },
693
+
694
+ gesture(event) {
695
+ const { options } = this;
696
+
697
+ if (!this.viewed || (!options.zoomOnGesture && !options.rotateOnGesture)) {
698
+ return;
699
+ }
700
+
701
+ event.preventDefault();
702
+
703
+ switch (event.type) {
704
+ case 'gesturestart':
705
+ this.gesturing = true;
706
+ this.gestureScale = event.scale || 1;
707
+ this.gestureRotation = event.rotation || 0;
708
+ break;
709
+
710
+ case 'gesturechange': {
711
+ const scale = event.scale || 1;
712
+ const ratio = scale / (this.gestureScale || 1);
713
+ const rotation = Number(event.rotation);
714
+ const degree = rotation - (this.gestureRotation || 0);
715
+
716
+ this.gestureScale = scale;
717
+
718
+ if (options.zoomable && options.zoomOnGesture && ratio !== 1) {
719
+ this.zoom(ratio >= 1 ? ratio - 1 : 1 - (1 / ratio), false, null, event);
720
+ }
721
+
722
+ if (options.rotatable && options.rotateOnGesture && isNumber(rotation)) {
723
+ this.gestureRotation = rotation;
724
+
725
+ if (degree !== 0) {
726
+ this.rotate(degree);
727
+ }
728
+ }
729
+
730
+ break;
731
+ }
732
+
733
+ case 'gestureend':
734
+ this.gesturing = false;
735
+ this.gestureScale = 1;
736
+ this.gestureRotation = 0;
737
+ break;
738
+
739
+ default:
740
+ }
741
+ },
554
742
  };