viewerjs 1.12.0 → 1.14.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}
@@ -161,9 +197,18 @@ export default {
161
197
  zoomOnTouch: true,
162
198
 
163
199
  /**
164
- * Enable to zoom the image by wheeling mouse.
200
+ * Enable to zoom the current image by gesture.
165
201
  * @type {boolean}
166
202
  */
203
+ zoomOnGesture: true,
204
+
205
+ /**
206
+ * Enable to zoom the image by wheeling mouse.
207
+ * Set to a modifier key name (`ctrl`, `shift`, `alt`, `meta`), or a
208
+ * combination joined with `+` (e.g. `ctrl+shift`), to only zoom when
209
+ * the given modifier key(s) are held down while wheeling.
210
+ * @type {boolean | string}
211
+ */
167
212
  zoomOnWheel: true,
168
213
 
169
214
  /**
@@ -172,6 +217,17 @@ export default {
172
217
  */
173
218
  slideOnTouch: true,
174
219
 
220
+ /**
221
+ * Enable to slide to the next or previous image by wheeling mouse.
222
+ * Set to a modifier key name (`ctrl`, `shift`, `alt`, `meta`), or a
223
+ * combination joined with `+` (e.g. `ctrl+shift`), to only slide when
224
+ * the given modifier key(s) are held down while wheeling.
225
+ * Takes effect over the navbar, and also over the rest of the viewer
226
+ * when `zoomOnWheel` doesn't take effect for the wheel event.
227
+ * @type {boolean | string}
228
+ */
229
+ slideOnWheel: true,
230
+
175
231
  /**
176
232
  * Indicate if toggle the image size between its natural size
177
233
  * and initial size when double click on the image or not.
@@ -187,7 +243,7 @@ export default {
187
243
 
188
244
  /**
189
245
  * Enable CSS3 Transition for some special elements.
190
- * @type {boolean}
246
+ * @type {boolean | Object}
191
247
  */
192
248
  transition: true,
193
249
 
@@ -211,13 +267,13 @@ export default {
211
267
 
212
268
  /**
213
269
  * Define the min ratio of the image when zoom out.
214
- * @type {number}
270
+ * @type {number | Function}
215
271
  */
216
272
  minZoomRatio: 0.01,
217
273
 
218
274
  /**
219
275
  * Define the max ratio of the image when zoom in.
220
- * @type {number}
276
+ * @type {number | Function}
221
277
  */
222
278
  maxZoomRatio: 100,
223
279
 
@@ -247,5 +303,6 @@ export default {
247
303
  zoom: null,
248
304
  zoomed: null,
249
305
  play: null,
306
+ playing: null,
250
307
  stop: null,
251
308
  };
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,23 +20,37 @@ 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)));
27
37
  addListener(document, EVENT_KEY_DOWN, (this.onKeyDown = this.keydown.bind(this)));
28
38
  addListener(window, EVENT_RESIZE, (this.onResize = this.resize.bind(this)));
29
39
 
30
- if (options.zoomable && options.zoomOnWheel) {
40
+ if ((options.zoomable && options.zoomOnWheel) || options.slideOnWheel) {
31
41
  addListener(viewer, EVENT_WHEEL, (this.onWheel = this.wheel.bind(this)), {
32
42
  passive: false,
33
43
  capture: true,
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,23 +58,33 @@ 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);
51
71
  removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);
52
72
  removeListener(window, EVENT_RESIZE, this.onResize);
53
73
 
54
- if (options.zoomable && options.zoomOnWheel) {
74
+ if ((options.zoomable && options.zoomOnWheel) || options.slideOnWheel) {
55
75
  removeListener(viewer, EVENT_WHEEL, this.onWheel, {
56
76
  passive: false,
57
77
  capture: true,
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,10 @@ import {
25
27
  getTransforms,
26
28
  isFunction,
27
29
  isNumber,
30
+ isPlainObject,
31
+ isTransitionEnabled,
32
+ isUndefined,
33
+ isWheelActionEnabled,
28
34
  removeClass,
29
35
  setStyle,
30
36
  toggleClass,
@@ -46,6 +52,8 @@ export default {
46
52
  clearTimeout(this.clickCanvasTimeout);
47
53
  }
48
54
 
55
+ this.actionEvent = event;
56
+
49
57
  switch (action) {
50
58
  case 'mix':
51
59
  if (this.played) {
@@ -133,7 +141,8 @@ export default {
133
141
  }
134
142
 
135
143
  // XXX: No pageX/Y properties in custom event, fallback to the original event.
136
- this.toggle(event.isTrusted ? event : (event.detail && event.detail.originalEvent));
144
+ this.actionEvent = event.isTrusted ? event : (event.detail && event.detail.originalEvent);
145
+ this.toggle();
137
146
  }
138
147
  },
139
148
 
@@ -168,11 +177,18 @@ export default {
168
177
 
169
178
  this.initImage(() => {
170
179
  toggleClass(image, CLASS_MOVE, options.movable);
171
- toggleClass(image, CLASS_TRANSITION, options.transition);
180
+ toggleClass(
181
+ image,
182
+ CLASS_TRANSITION,
183
+ isTransitionEnabled(options, 'view'),
184
+ );
172
185
 
173
186
  this.renderImage(() => {
174
187
  this.viewed = true;
175
188
  this.viewing = false;
189
+ setTimeout(() => {
190
+ toggleClass(image, CLASS_TRANSITION, options.transition);
191
+ }, 300);
176
192
 
177
193
  if (isFunction(options.viewed)) {
178
194
  addListener(element, EVENT_VIEWED, options.viewed, {
@@ -184,9 +200,12 @@ export default {
184
200
  originalImage: this.images[index],
185
201
  index,
186
202
  image,
203
+ originalEvent: this.viewOriginalEvent || null,
187
204
  }, {
188
205
  cancelable: false,
189
206
  });
207
+
208
+ this.viewOriginalEvent = null;
190
209
  });
191
210
  });
192
211
  },
@@ -234,6 +253,8 @@ export default {
234
253
 
235
254
  const keyCode = event.keyCode || event.which || event.charCode;
236
255
 
256
+ this.actionEvent = event;
257
+
237
258
  switch (keyCode) {
238
259
  // Enter
239
260
  case 13:
@@ -333,6 +354,105 @@ export default {
333
354
  }
334
355
  },
335
356
 
357
+ magnify(event) {
358
+ if (event.changedTouches || (event.pointerType && event.pointerType !== 'mouse')) {
359
+ this.hideMagnifier();
360
+ return;
361
+ }
362
+
363
+ this.magnifierPoint = {
364
+ clientX: event.clientX,
365
+ clientY: event.clientY,
366
+ };
367
+ this.renderMagnifier();
368
+ },
369
+
370
+ renderMagnifier() {
371
+ const {
372
+ options,
373
+ imageData,
374
+ magnifier,
375
+ magnifierImage,
376
+ viewer,
377
+ } = this;
378
+ const config = isPlainObject(options.magnifier) ? options.magnifier : {};
379
+ const point = this.magnifierPoint;
380
+
381
+ if (!options.magnifier || !this.fulled || !this.viewed || !magnifier
382
+ || !magnifierImage || !point) {
383
+ this.hideMagnifier();
384
+ return;
385
+ }
386
+
387
+ const size = Math.max(1, Number(config.size) || 100);
388
+ const zoomRatio = Math.max(1, Number(config.zoomRatio) || 2);
389
+ const opacity = Number(config.opacity);
390
+ const rect = viewer.getBoundingClientRect();
391
+ const x = point.clientX - rect.left;
392
+ const y = point.clientY - rect.top;
393
+ const imageURL = this.image.currentSrc || this.image.src;
394
+ const imageRect = this.image.getBoundingClientRect();
395
+
396
+ if (point.clientX < imageRect.left || point.clientX > imageRect.right
397
+ || point.clientY < imageRect.top || point.clientY > imageRect.bottom) {
398
+ this.hideMagnifier();
399
+ return;
400
+ }
401
+
402
+ const scaleX = isNumber(imageData.scaleX) ? imageData.scaleX : 1;
403
+ const scaleY = isNumber(imageData.scaleY) ? imageData.scaleY : 1;
404
+ const rotate = ((imageData.rotate || 0) * Math.PI) / 180;
405
+ const cos = Math.cos(rotate);
406
+ const sin = Math.sin(rotate);
407
+ const matrixA = cos * scaleX;
408
+ const matrixB = sin * scaleX;
409
+ const matrixC = -sin * scaleY;
410
+ const matrixD = cos * scaleY;
411
+ const determinant = scaleX * scaleY;
412
+
413
+ if (determinant === 0) {
414
+ this.hideMagnifier();
415
+ return;
416
+ }
417
+
418
+ const centerX = imageData.x + (imageData.width / 2);
419
+ const centerY = imageData.y + (imageData.height / 2);
420
+ const localX = (matrixD * (x - centerX) - matrixC * (y - centerY)) / determinant;
421
+ const localY = (-matrixB * (x - centerX) + matrixA * (y - centerY)) / determinant;
422
+ const sourceX = localX + imageData.width / 2;
423
+ const sourceY = localY + imageData.height / 2;
424
+ const magnifiedWidth = imageData.width * zoomRatio;
425
+ const magnifiedHeight = imageData.height * zoomRatio;
426
+ const transformedX = matrixA * (sourceX * zoomRatio - magnifiedWidth / 2)
427
+ + matrixC * (sourceY * zoomRatio - magnifiedHeight / 2);
428
+ const transformedY = matrixB * (sourceX * zoomRatio - magnifiedWidth / 2)
429
+ + matrixD * (sourceY * zoomRatio - magnifiedHeight / 2);
430
+
431
+ this.magnifierSourcePoint = {
432
+ x: sourceX,
433
+ y: sourceY,
434
+ };
435
+ magnifier.style.width = `${size}px`;
436
+ magnifier.style.height = `${size}px`;
437
+ magnifier.style.opacity = `${Math.max(0, Math.min(1, isNumber(opacity) ? opacity : 1))}`;
438
+ magnifierImage.src = imageURL;
439
+ magnifierImage.style.width = `${magnifiedWidth}px`;
440
+ magnifierImage.style.height = `${magnifiedHeight}px`;
441
+ magnifierImage.style.left = `${size / 2 - magnifiedWidth / 2 - transformedX}px`;
442
+ magnifierImage.style.top = `${size / 2 - magnifiedHeight / 2 - transformedY}px`;
443
+ magnifierImage.style.transform = `rotate(${imageData.rotate || 0}deg) scaleX(${scaleX}) scaleY(${scaleY})`;
444
+ magnifier.removeAttribute('aria-hidden');
445
+ addClass(magnifier, 'viewer-show');
446
+ },
447
+
448
+ hideMagnifier() {
449
+ if (this.magnifier) {
450
+ removeClass(this.magnifier, 'viewer-show');
451
+ this.magnifier.setAttribute('aria-hidden', true);
452
+ this.magnifierPoint = null;
453
+ }
454
+ },
455
+
336
456
  pointerdown(event) {
337
457
  const { options, pointers } = this;
338
458
  const { buttons, button } = event;
@@ -374,13 +494,21 @@ export default {
374
494
 
375
495
  let action = options.movable ? ACTION_MOVE : false;
376
496
 
377
- if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {
378
- action = ACTION_ZOOM;
497
+ if ((
498
+ (options.zoomable && options.zoomOnTouch)
499
+ || (options.rotatable && options.rotateOnTouch)
500
+ ) && Object.keys(pointers).length > 1) {
501
+ // action = ACTION_ZOOM;
502
+ // action = ACTION_ROTATE;
503
+ action = ACTION_TRANSFORM;
379
504
  } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {
380
505
  action = ACTION_SWITCH;
381
506
  }
382
507
 
383
- if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
508
+ if (action === ACTION_MOVE
509
+ || action === ACTION_ZOOM
510
+ || action === ACTION_ROTATE
511
+ || action === ACTION_TRANSFORM) {
384
512
  removeClass(this.image, CLASS_TRANSITION);
385
513
  }
386
514
 
@@ -427,8 +555,16 @@ export default {
427
555
 
428
556
  event.preventDefault();
429
557
 
430
- if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
431
- addClass(this.image, CLASS_TRANSITION);
558
+ if (action === ACTION_MOVE
559
+ || action === ACTION_ZOOM
560
+ || action === ACTION_ROTATE
561
+ || action === ACTION_TRANSFORM) {
562
+ const transition = action === ACTION_TRANSFORM
563
+ ? isTransitionEnabled(options, ACTION_ZOOM)
564
+ || isTransitionEnabled(options, ACTION_ROTATE)
565
+ : isTransitionEnabled(options, action);
566
+
567
+ toggleClass(this.image, CLASS_TRANSITION, transition);
432
568
  }
433
569
 
434
570
  this.action = false;
@@ -437,6 +573,7 @@ export default {
437
573
  if (
438
574
  IS_TOUCH_DEVICE
439
575
  && action !== ACTION_ZOOM
576
+ && action !== ACTION_TRANSFORM
440
577
  && pointer
441
578
  && (Date.now() - pointer.timeStamp < 500)
442
579
  ) {
@@ -492,6 +629,13 @@ export default {
492
629
  this.initContainer();
493
630
  this.initViewer();
494
631
  this.renderViewer();
632
+
633
+ const navbarOptions = isPlainObject(this.options.navbar) ? this.options.navbar : {};
634
+
635
+ if (isUndefined(navbarOptions.visibleItemCount)) {
636
+ this.initList(this.index);
637
+ }
638
+
495
639
  this.renderList();
496
640
 
497
641
  if (this.viewed) {
@@ -521,13 +665,19 @@ export default {
521
665
  },
522
666
 
523
667
  wheel(event) {
668
+ const { options, navbar } = this;
669
+
524
670
  if (!this.viewed) {
525
671
  return;
526
672
  }
527
673
 
528
674
  event.preventDefault();
529
675
 
530
- // Limit wheel speed to prevent zoom too fast
676
+ if (this.gesturing) {
677
+ return;
678
+ }
679
+
680
+ // Limit wheel speed to prevent zoom or slide too fast
531
681
  if (this.wheeling) {
532
682
  return;
533
683
  }
@@ -538,7 +688,6 @@ export default {
538
688
  this.wheeling = false;
539
689
  }, 50);
540
690
 
541
- const ratio = Number(this.options.zoomRatio) || 0.1;
542
691
  let delta = 1;
543
692
 
544
693
  if (event.deltaY) {
@@ -549,6 +698,79 @@ export default {
549
698
  delta = event.detail > 0 ? 1 : -1;
550
699
  }
551
700
 
552
- this.zoom(-delta * ratio, true, null, event);
701
+ // Wheeling over the navbar never zooms, only slides
702
+ const overNavbar = navbar && navbar.contains(event.target);
703
+ const zoomable = !overNavbar
704
+ && options.zoomable
705
+ && isWheelActionEnabled(options.zoomOnWheel, event);
706
+
707
+ if (zoomable) {
708
+ const ratio = Number(options.zoomRatio) || 0.1;
709
+
710
+ this.actionEvent = event;
711
+ this.zoom(-delta * ratio, true);
712
+ return;
713
+ }
714
+
715
+ if (isWheelActionEnabled(options.slideOnWheel, event)) {
716
+ this.actionEvent = event;
717
+
718
+ if (delta > 0) {
719
+ this.next(options.loop);
720
+ } else if (delta < 0) {
721
+ this.prev(options.loop);
722
+ }
723
+ }
724
+ },
725
+
726
+ gesture(event) {
727
+ const { options } = this;
728
+
729
+ if (!this.viewed || (!options.zoomOnGesture && !options.rotateOnGesture)) {
730
+ return;
731
+ }
732
+
733
+ event.preventDefault();
734
+
735
+ switch (event.type) {
736
+ case 'gesturestart':
737
+ this.gesturing = true;
738
+ this.gestureScale = event.scale || 1;
739
+ this.gestureRotation = event.rotation || 0;
740
+ break;
741
+
742
+ case 'gesturechange': {
743
+ const scale = event.scale || 1;
744
+ const ratio = scale / (this.gestureScale || 1);
745
+ const rotation = Number(event.rotation);
746
+ const degree = rotation - (this.gestureRotation || 0);
747
+
748
+ this.gestureScale = scale;
749
+
750
+ if (options.zoomable && options.zoomOnGesture && ratio !== 1) {
751
+ this.actionEvent = event;
752
+ this.zoom(ratio >= 1 ? ratio - 1 : 1 - (1 / ratio));
753
+ }
754
+
755
+ if (options.rotatable && options.rotateOnGesture && isNumber(rotation)) {
756
+ this.gestureRotation = rotation;
757
+
758
+ if (degree !== 0) {
759
+ this.actionEvent = event;
760
+ this.rotate(degree);
761
+ }
762
+ }
763
+
764
+ break;
765
+ }
766
+
767
+ case 'gestureend':
768
+ this.gesturing = false;
769
+ this.gestureScale = 1;
770
+ this.gestureRotation = 0;
771
+ break;
772
+
773
+ default:
774
+ }
553
775
  },
554
776
  };