@hyper-proto/iv-viewer 2.3.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.
- package/README.md +249 -0
- package/dist/iv-viewer.css +221 -0
- package/dist/iv-viewer.es.js +1432 -0
- package/dist/iv-viewer.js +1442 -0
- package/dist/iv-viewer.min.css +1 -0
- package/dist/iv-viewer.min.js +1 -0
- package/iv-viewer.d.ts +54 -0
- package/lib/FullScreen.js +114 -0
- package/lib/ImageViewer.js +962 -0
- package/lib/Slider.js +109 -0
- package/lib/dist.js +11 -0
- package/lib/index.js +22 -0
- package/lib/iv-viewer.css +221 -0
- package/lib/util.js +174 -0
- package/package.json +60 -0
- package/src/FullScreen.js +76 -0
- package/src/ImageViewer.js +988 -0
- package/src/Slider.js +97 -0
- package/src/dist.js +6 -0
- package/src/index.js +5 -0
- package/src/scss/_iv-viewer.scss +268 -0
- package/src/scss/_variables.scss +43 -0
- package/src/scss/build.scss +1 -0
- package/src/util.js +158 -0
|
@@ -0,0 +1,988 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createElement,
|
|
3
|
+
addClass,
|
|
4
|
+
removeClass,
|
|
5
|
+
css,
|
|
6
|
+
removeCss,
|
|
7
|
+
wrap,
|
|
8
|
+
unwrap,
|
|
9
|
+
remove,
|
|
10
|
+
easeOutQuart,
|
|
11
|
+
imageLoaded,
|
|
12
|
+
clamp,
|
|
13
|
+
assignEvent,
|
|
14
|
+
getTouchPointsDistance,
|
|
15
|
+
preventDefault,
|
|
16
|
+
ZOOM_CONSTANT,
|
|
17
|
+
MOUSE_WHEEL_COUNT,
|
|
18
|
+
} from './util';
|
|
19
|
+
|
|
20
|
+
import Slider from './Slider';
|
|
21
|
+
class ImageViewer {
|
|
22
|
+
get zoomInButton () {
|
|
23
|
+
return this._options.hasZoomButtons ? '<div class="iv-button-zoom--in" role="button"></div>' : '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get zoomOutButton () {
|
|
27
|
+
return this._options.hasZoomButtons ? '<div class="iv-button-zoom--out" role="button"></div>' : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get imageViewHtml () {
|
|
31
|
+
return `
|
|
32
|
+
<div class="iv-loader"></div>
|
|
33
|
+
<div class="iv-snap-view">
|
|
34
|
+
<div class="iv-snap-image-wrap">
|
|
35
|
+
<div class="iv-snap-handle"></div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="iv-zoom-actions ${this._options.hasZoomButtons ? 'iv-zoom-actions--has-buttons' : ''}">
|
|
38
|
+
${this.zoomInButton}
|
|
39
|
+
<div class="iv-zoom-slider">
|
|
40
|
+
<div class="iv-zoom-handle"></div>
|
|
41
|
+
</div>
|
|
42
|
+
${this.zoomOutButton}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="iv-image-view" >
|
|
46
|
+
<div class="iv-image-wrap" ></div>
|
|
47
|
+
</div>
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
constructor (element, options = {}) {
|
|
52
|
+
const { container, domElement, imageSrc, hiResImageSrc } = this._findContainerAndImageSrc(element, options);
|
|
53
|
+
|
|
54
|
+
// containers for elements
|
|
55
|
+
this._elements = {
|
|
56
|
+
container,
|
|
57
|
+
domElement,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this._options = { ...ImageViewer.defaults, ...options };
|
|
61
|
+
|
|
62
|
+
// container for all events
|
|
63
|
+
this._events = {
|
|
64
|
+
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
this._listeners = this._options.listeners || {};
|
|
68
|
+
|
|
69
|
+
// container for all timeout and frames
|
|
70
|
+
this._frames = {
|
|
71
|
+
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// container for all sliders
|
|
75
|
+
this._sliders = {
|
|
76
|
+
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// maintain current state
|
|
80
|
+
this._state = {
|
|
81
|
+
zoomValue: this._options.zoomValue,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
this._images = {
|
|
85
|
+
imageSrc,
|
|
86
|
+
hiResImageSrc,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this._init();
|
|
90
|
+
|
|
91
|
+
if (imageSrc) {
|
|
92
|
+
this._loadImages();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// store reference of imageViewer in domElement
|
|
96
|
+
domElement._imageViewer = this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_findContainerAndImageSrc (element) {
|
|
100
|
+
let domElement = element;
|
|
101
|
+
let imageSrc, hiResImageSrc;
|
|
102
|
+
|
|
103
|
+
if (typeof element === 'string') {
|
|
104
|
+
domElement = document.querySelector(element);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// throw error if imageViewer is already assigned
|
|
108
|
+
if (domElement._imageViewer) {
|
|
109
|
+
throw new Error('An image viewer is already being initiated on the element.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let container = element;
|
|
113
|
+
|
|
114
|
+
if (domElement.tagName === 'IMG') {
|
|
115
|
+
imageSrc = domElement.src;
|
|
116
|
+
hiResImageSrc = domElement.getAttribute('high-res-src') || domElement.getAttribute('data-high-res-src');
|
|
117
|
+
|
|
118
|
+
// wrap the image with iv-container div
|
|
119
|
+
container = wrap(domElement, { className: 'iv-container iv-image-mode', style: { display: 'inline-block', overflow: 'hidden' } });
|
|
120
|
+
|
|
121
|
+
// hide the image and add iv-original-img class
|
|
122
|
+
css(domElement, {
|
|
123
|
+
opacity: 0,
|
|
124
|
+
position: 'relative',
|
|
125
|
+
zIndex: -1,
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
imageSrc = domElement.getAttribute('src') || domElement.getAttribute('data-src');
|
|
129
|
+
hiResImageSrc = domElement.getAttribute('high-res-src') || domElement.getAttribute('data-high-res-src');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
container,
|
|
134
|
+
domElement,
|
|
135
|
+
imageSrc,
|
|
136
|
+
hiResImageSrc,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_init () {
|
|
141
|
+
// initialize the dom elements
|
|
142
|
+
this._initDom();
|
|
143
|
+
|
|
144
|
+
// initialize slider
|
|
145
|
+
this._initImageSlider();
|
|
146
|
+
this._initSnapSlider();
|
|
147
|
+
this._initZoomSlider();
|
|
148
|
+
|
|
149
|
+
// enable pinch and zoom feature for touch screens
|
|
150
|
+
this._pinchAndZoom();
|
|
151
|
+
|
|
152
|
+
// enable scroll zoom interaction
|
|
153
|
+
this._scrollZoom();
|
|
154
|
+
|
|
155
|
+
// enable double tap to zoom interaction
|
|
156
|
+
this._doubleTapToZoom();
|
|
157
|
+
|
|
158
|
+
// initialize events
|
|
159
|
+
this._initEvents();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_initDom () {
|
|
163
|
+
const { container } = this._elements;
|
|
164
|
+
|
|
165
|
+
// add image-viewer layout elements
|
|
166
|
+
createElement({
|
|
167
|
+
tagName: 'div',
|
|
168
|
+
className: 'iv-wrap',
|
|
169
|
+
html: this.imageViewHtml,
|
|
170
|
+
parent: container,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// add container class on the container
|
|
174
|
+
addClass(container, 'iv-container');
|
|
175
|
+
|
|
176
|
+
// if the element is static position, position it relatively
|
|
177
|
+
if (css(container, 'position') === 'static') {
|
|
178
|
+
css(container, { position: 'relative' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// save references for later use
|
|
182
|
+
this._elements = {
|
|
183
|
+
...this._elements,
|
|
184
|
+
snapView: container.querySelector('.iv-snap-view'),
|
|
185
|
+
snapImageWrap: container.querySelector('.iv-snap-image-wrap'),
|
|
186
|
+
imageWrap: container.querySelector('.iv-image-wrap'),
|
|
187
|
+
snapHandle: container.querySelector('.iv-snap-handle'),
|
|
188
|
+
zoomHandle: container.querySelector('.iv-zoom-handle'),
|
|
189
|
+
zoomIn: container.querySelector('.iv-button-zoom--in'),
|
|
190
|
+
zoomOut: container.querySelector('.iv-button-zoom--out'),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (this._listeners.onInit) {
|
|
194
|
+
this._listeners.onInit(this._callbackData);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_initImageSlider () {
|
|
199
|
+
const {
|
|
200
|
+
_elements,
|
|
201
|
+
} = this;
|
|
202
|
+
|
|
203
|
+
const { imageWrap } = _elements;
|
|
204
|
+
|
|
205
|
+
let positions, currentPos;
|
|
206
|
+
|
|
207
|
+
/* Add slide interaction to image */
|
|
208
|
+
const imageSlider = new Slider(imageWrap, {
|
|
209
|
+
isSliderEnabled: () => {
|
|
210
|
+
const { loaded, zooming, zoomValue } = this._state;
|
|
211
|
+
return loaded && !zooming && zoomValue > 100;
|
|
212
|
+
},
|
|
213
|
+
onStart: (e, position) => {
|
|
214
|
+
const { snapSlider } = this._sliders;
|
|
215
|
+
|
|
216
|
+
// clear all animation frame and interval
|
|
217
|
+
this._clearFrames();
|
|
218
|
+
|
|
219
|
+
snapSlider.onStart();
|
|
220
|
+
|
|
221
|
+
// reset positions
|
|
222
|
+
positions = [position, position];
|
|
223
|
+
currentPos = undefined;
|
|
224
|
+
|
|
225
|
+
this._frames.slideMomentumCheck = setInterval(() => {
|
|
226
|
+
if (!currentPos) return;
|
|
227
|
+
|
|
228
|
+
positions.shift();
|
|
229
|
+
positions.push({
|
|
230
|
+
x: currentPos.mx,
|
|
231
|
+
y: currentPos.my,
|
|
232
|
+
});
|
|
233
|
+
}, 50);
|
|
234
|
+
},
|
|
235
|
+
onMove: (e, position) => {
|
|
236
|
+
const { snapImageDim } = this._state;
|
|
237
|
+
const { snapSlider } = this._sliders;
|
|
238
|
+
const imageCurrentDim = this._getImageCurrentDim();
|
|
239
|
+
currentPos = position;
|
|
240
|
+
|
|
241
|
+
snapSlider.onMove(e, {
|
|
242
|
+
dx: -position.dx * snapImageDim.w / imageCurrentDim.w,
|
|
243
|
+
dy: -position.dy * snapImageDim.h / imageCurrentDim.h,
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
onEnd: () => {
|
|
247
|
+
const { snapImageDim } = this._state;
|
|
248
|
+
const { snapSlider } = this._sliders;
|
|
249
|
+
const imageCurrentDim = this._getImageCurrentDim();
|
|
250
|
+
|
|
251
|
+
// clear all animation frame and interval
|
|
252
|
+
this._clearFrames();
|
|
253
|
+
|
|
254
|
+
let step, positionX, positionY;
|
|
255
|
+
|
|
256
|
+
const xDiff = positions[1].x - positions[0].x;
|
|
257
|
+
const yDiff = positions[1].y - positions[0].y;
|
|
258
|
+
|
|
259
|
+
const momentum = () => {
|
|
260
|
+
if (step <= 60) {
|
|
261
|
+
this._frames.sliderMomentumFrame = requestAnimationFrame(momentum);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
positionX += easeOutQuart(step, xDiff / 3, -xDiff / 3, 60);
|
|
265
|
+
positionY += easeOutQuart(step, yDiff / 3, -yDiff / 3, 60);
|
|
266
|
+
|
|
267
|
+
snapSlider.onMove(null, {
|
|
268
|
+
dx: -(positionX * snapImageDim.w / imageCurrentDim.w),
|
|
269
|
+
dy: -(positionY * snapImageDim.h / imageCurrentDim.h),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
step++;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (Math.abs(xDiff) > 30 || Math.abs(yDiff) > 30) {
|
|
276
|
+
step = 1;
|
|
277
|
+
positionX = currentPos.dx;
|
|
278
|
+
positionY = currentPos.dy;
|
|
279
|
+
|
|
280
|
+
momentum();
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
imageSlider.init();
|
|
286
|
+
|
|
287
|
+
this._sliders.imageSlider = imageSlider;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_initSnapSlider () {
|
|
291
|
+
const {
|
|
292
|
+
snapHandle,
|
|
293
|
+
} = this._elements;
|
|
294
|
+
|
|
295
|
+
let startHandleTop, startHandleLeft;
|
|
296
|
+
|
|
297
|
+
const snapSlider = new Slider(snapHandle, {
|
|
298
|
+
isSliderEnabled: () => this._state.loaded,
|
|
299
|
+
onStart: () => {
|
|
300
|
+
const { slideMomentumCheck, sliderMomentumFrame } = this._frames;
|
|
301
|
+
|
|
302
|
+
startHandleTop = parseFloat(css(snapHandle, 'top'));
|
|
303
|
+
startHandleLeft = parseFloat(css(snapHandle, 'left'));
|
|
304
|
+
|
|
305
|
+
// stop momentum on image
|
|
306
|
+
clearInterval(slideMomentumCheck);
|
|
307
|
+
cancelAnimationFrame(sliderMomentumFrame);
|
|
308
|
+
},
|
|
309
|
+
onMove: (e, position) => {
|
|
310
|
+
const { snapHandleDim, snapImageDim } = this._state;
|
|
311
|
+
const { image } = this._elements;
|
|
312
|
+
|
|
313
|
+
const imageCurrentDim = this._getImageCurrentDim();
|
|
314
|
+
|
|
315
|
+
// find handle left and top and make sure they lay between the snap image
|
|
316
|
+
const maxLeft = Math.max(snapImageDim.w - snapHandleDim.w, startHandleLeft);
|
|
317
|
+
const maxTop = Math.max(snapImageDim.h - snapHandleDim.h, startHandleTop);
|
|
318
|
+
const minLeft = Math.min(0, startHandleLeft);
|
|
319
|
+
const minTop = Math.min(0, startHandleTop);
|
|
320
|
+
|
|
321
|
+
const left = clamp(startHandleLeft + position.dx, minLeft, maxLeft);
|
|
322
|
+
const top = clamp(startHandleTop + position.dy, minTop, maxTop);
|
|
323
|
+
|
|
324
|
+
const imgLeft = -left * imageCurrentDim.w / snapImageDim.w;
|
|
325
|
+
const imgTop = -top * imageCurrentDim.h / snapImageDim.h;
|
|
326
|
+
|
|
327
|
+
css(snapHandle, {
|
|
328
|
+
left: `${left}px`,
|
|
329
|
+
top: `${top}px`,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
css(image, {
|
|
333
|
+
left: `${imgLeft}px`,
|
|
334
|
+
top: `${imgTop}px`,
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
snapSlider.init();
|
|
340
|
+
|
|
341
|
+
this._sliders.snapSlider = snapSlider;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_initZoomSlider () {
|
|
345
|
+
const { snapView, zoomHandle } = this._elements;
|
|
346
|
+
|
|
347
|
+
// zoom in zoom out using zoom handle
|
|
348
|
+
const sliderElm = snapView.querySelector('.iv-zoom-slider');
|
|
349
|
+
|
|
350
|
+
let leftOffset, handleWidth;
|
|
351
|
+
|
|
352
|
+
// on zoom slider we have to follow the mouse and set the handle to its position.
|
|
353
|
+
const zoomSlider = new Slider(sliderElm, {
|
|
354
|
+
isSliderEnabled: () => this._state.loaded,
|
|
355
|
+
onStart: (eStart) => {
|
|
356
|
+
const { zoomSlider: slider } = this._sliders;
|
|
357
|
+
|
|
358
|
+
leftOffset = sliderElm.getBoundingClientRect().left;
|
|
359
|
+
handleWidth = parseInt(css(zoomHandle, 'width'), 10);
|
|
360
|
+
|
|
361
|
+
// move the handle to current mouse position
|
|
362
|
+
slider.onMove(eStart);
|
|
363
|
+
},
|
|
364
|
+
onMove: (e) => {
|
|
365
|
+
const { maxZoom } = this._options;
|
|
366
|
+
const { zoomSliderLength } = this._state;
|
|
367
|
+
|
|
368
|
+
const clientX = e.clientX !== undefined ? e.clientX : e.touches[0].clientX;
|
|
369
|
+
|
|
370
|
+
const newLeft = clamp(clientX - leftOffset - handleWidth / 2, 0, zoomSliderLength);
|
|
371
|
+
|
|
372
|
+
const zoomValue = 100 + ((maxZoom - 100) * newLeft / zoomSliderLength);
|
|
373
|
+
|
|
374
|
+
this.zoom(zoomValue);
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
zoomSlider.init();
|
|
379
|
+
this._sliders.zoomSlider = zoomSlider;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
_initEvents () {
|
|
383
|
+
this._snapViewEvents();
|
|
384
|
+
|
|
385
|
+
// handle window resize
|
|
386
|
+
if (this._options.refreshOnResize) {
|
|
387
|
+
this._events.onWindowResize = assignEvent(window, 'resize', this.refresh);
|
|
388
|
+
}
|
|
389
|
+
this._events.onDragStart = assignEvent(this._elements.container, 'dragstart', preventDefault);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
_snapViewEvents () {
|
|
393
|
+
const { imageWrap, snapView } = this._elements;
|
|
394
|
+
|
|
395
|
+
// show snapView on mouse move
|
|
396
|
+
this._events.snapViewOnMouseMove = assignEvent(imageWrap, ['touchmove', 'mousemove'], () => {
|
|
397
|
+
this.showSnapView();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// keep showing snapView if on hover over it without any timeout
|
|
401
|
+
this._events.mouseEnterSnapView = assignEvent(snapView, ['mouseenter', 'touchstart'], () => {
|
|
402
|
+
this._state.snapViewVisible = false;
|
|
403
|
+
this.showSnapView(true);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// on mouse leave set timeout to hide snapView
|
|
407
|
+
this._events.mouseLeaveSnapView = assignEvent(snapView, ['mouseleave', 'touchend'], () => {
|
|
408
|
+
this._state.snapViewVisible = false;
|
|
409
|
+
this.showSnapView();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
if (!this._options.hasZoomButtons) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const { zoomOut, zoomIn } = this._elements;
|
|
416
|
+
this._events.zoomInClick = assignEvent(zoomIn, ['click'], () => {
|
|
417
|
+
this.zoom(this._state.zoomValue + this._options.zoomStep || 50);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
this._events.zoomOutClick = assignEvent(zoomOut, ['click'], () => {
|
|
421
|
+
this.zoom(this._state.zoomValue - this._options.zoomStep || 50);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
_pinchAndZoom () {
|
|
426
|
+
const { imageWrap, container } = this._elements;
|
|
427
|
+
|
|
428
|
+
// apply pinch and zoom feature
|
|
429
|
+
const onPinchStart = (eStart) => {
|
|
430
|
+
const { loaded, zoomValue: startZoomValue } = this._state;
|
|
431
|
+
const { _events: events } = this;
|
|
432
|
+
|
|
433
|
+
if (!loaded) return;
|
|
434
|
+
|
|
435
|
+
const touch0 = eStart.touches[0];
|
|
436
|
+
const touch1 = eStart.touches[1];
|
|
437
|
+
|
|
438
|
+
if (!(touch0 && touch1)) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this._state.zooming = true;
|
|
443
|
+
|
|
444
|
+
const contOffset = container.getBoundingClientRect();
|
|
445
|
+
|
|
446
|
+
// find distance between two touch points
|
|
447
|
+
const startDist = getTouchPointsDistance(eStart.touches);
|
|
448
|
+
|
|
449
|
+
// find the center for the zoom
|
|
450
|
+
const center = {
|
|
451
|
+
x: (touch1.clientX + touch0.clientX) / 2 - contOffset.left,
|
|
452
|
+
y: (touch1.clientY + touch0.clientY) / 2 - contOffset.top,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const moveListener = (eMove) => {
|
|
456
|
+
// eMove.preventDefault();
|
|
457
|
+
|
|
458
|
+
const newDist = getTouchPointsDistance(eMove.touches);
|
|
459
|
+
|
|
460
|
+
const zoomValue = startZoomValue + (newDist - startDist) / 2;
|
|
461
|
+
|
|
462
|
+
this.zoom(zoomValue, center);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const endListener = (eEnd) => {
|
|
466
|
+
// unbind events
|
|
467
|
+
events.pinchMove();
|
|
468
|
+
events.pinchEnd();
|
|
469
|
+
this._state.zooming = false;
|
|
470
|
+
// properly resume move event if one finger remains
|
|
471
|
+
if (eEnd.touches.length === 1) {
|
|
472
|
+
this._sliders.imageSlider.startHandler(eEnd);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// remove events if already assigned
|
|
477
|
+
if (events.pinchMove) events.pinchMove();
|
|
478
|
+
if (events.pinchEnd) events.pinchEnd();
|
|
479
|
+
|
|
480
|
+
// assign events
|
|
481
|
+
events.pinchMove = assignEvent(document, 'touchmove', moveListener);
|
|
482
|
+
events.pinchEnd = assignEvent(document, 'touchend', endListener);
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
this._events.pinchStart = assignEvent(imageWrap, 'touchstart', onPinchStart);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
_scrollZoom () {
|
|
489
|
+
/* Add zoom interaction in mouse wheel */
|
|
490
|
+
const { _options } = this;
|
|
491
|
+
const { container, imageWrap } = this._elements;
|
|
492
|
+
|
|
493
|
+
let changedDelta = 0;
|
|
494
|
+
|
|
495
|
+
const onMouseWheel = (e) => {
|
|
496
|
+
const { loaded, zoomValue } = this._state;
|
|
497
|
+
|
|
498
|
+
if (!_options.zoomOnMouseWheel || !loaded) return;
|
|
499
|
+
|
|
500
|
+
// clear all animation frame and interval
|
|
501
|
+
this._clearFrames();
|
|
502
|
+
|
|
503
|
+
// cross-browser wheel delta
|
|
504
|
+
const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail || -e.deltaY));
|
|
505
|
+
|
|
506
|
+
const newZoomValue = zoomValue * (100 + delta * ZOOM_CONSTANT) / 100;
|
|
507
|
+
|
|
508
|
+
if (!(newZoomValue >= 100 && newZoomValue <= _options.maxZoom)) {
|
|
509
|
+
changedDelta += Math.abs(delta);
|
|
510
|
+
} else {
|
|
511
|
+
changedDelta = 0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
e.preventDefault();
|
|
515
|
+
|
|
516
|
+
if (changedDelta > MOUSE_WHEEL_COUNT) return;
|
|
517
|
+
|
|
518
|
+
const contOffset = container.getBoundingClientRect();
|
|
519
|
+
|
|
520
|
+
const x = e.clientX - contOffset.left;
|
|
521
|
+
const y = e.clientY - contOffset.top;
|
|
522
|
+
|
|
523
|
+
this.zoom(newZoomValue, {
|
|
524
|
+
x,
|
|
525
|
+
y,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// show the snap viewer
|
|
529
|
+
this.showSnapView();
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
this._events.scrollZoom = assignEvent(imageWrap, 'wheel', onMouseWheel);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
_doubleTapToZoom () {
|
|
536
|
+
const { imageWrap } = this._elements;
|
|
537
|
+
// handle double tap for zoom in and zoom out
|
|
538
|
+
|
|
539
|
+
let touchTime = 0;
|
|
540
|
+
|
|
541
|
+
let point;
|
|
542
|
+
|
|
543
|
+
const onDoubleTap = (e) => {
|
|
544
|
+
if (touchTime === 0) {
|
|
545
|
+
touchTime = Date.now();
|
|
546
|
+
point = {
|
|
547
|
+
x: e.clientX,
|
|
548
|
+
y: e.clientY,
|
|
549
|
+
};
|
|
550
|
+
} else if (Date.now() - touchTime < 500 && Math.abs(e.clientX - point.x) < 50 && Math.abs(e.clientY - point.y) < 50) {
|
|
551
|
+
if (this._state.zoomValue === this._options.zoomValue) {
|
|
552
|
+
this.zoom(200);
|
|
553
|
+
} else {
|
|
554
|
+
this.resetZoom();
|
|
555
|
+
}
|
|
556
|
+
touchTime = 0;
|
|
557
|
+
} else {
|
|
558
|
+
touchTime = 0;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
this._events.doubleTapToZoom = assignEvent(imageWrap, 'click', onDoubleTap);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_getImageCurrentDim () {
|
|
566
|
+
const { zoomValue, imageDim } = this._state;
|
|
567
|
+
return {
|
|
568
|
+
w: imageDim.w * (zoomValue / 100),
|
|
569
|
+
h: imageDim.h * (zoomValue / 100),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
_loadImages () {
|
|
574
|
+
const { _images, _elements } = this;
|
|
575
|
+
const { imageSrc, hiResImageSrc } = _images;
|
|
576
|
+
const { container, snapImageWrap, imageWrap } = _elements;
|
|
577
|
+
|
|
578
|
+
const ivLoader = container.querySelector('.iv-loader');
|
|
579
|
+
|
|
580
|
+
// remove old images
|
|
581
|
+
remove(container.querySelectorAll('.iv-snap-image, .iv-image'));
|
|
582
|
+
|
|
583
|
+
// add snapView image
|
|
584
|
+
const snapImage = createElement({
|
|
585
|
+
tagName: 'img',
|
|
586
|
+
className: 'iv-snap-image',
|
|
587
|
+
src: imageSrc,
|
|
588
|
+
insertBefore: snapImageWrap.firstChild,
|
|
589
|
+
parent: snapImageWrap,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// add image
|
|
593
|
+
const image = createElement({
|
|
594
|
+
tagName: 'img',
|
|
595
|
+
className: 'iv-image iv-small-image',
|
|
596
|
+
src: imageSrc,
|
|
597
|
+
parent: imageWrap,
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
this._state.loaded = false;
|
|
601
|
+
|
|
602
|
+
// store image reference in _elements
|
|
603
|
+
this._elements.image = image;
|
|
604
|
+
this._elements.snapImage = snapImage;
|
|
605
|
+
|
|
606
|
+
css(ivLoader, { display: 'block' });
|
|
607
|
+
|
|
608
|
+
// keep visibility hidden until image is loaded
|
|
609
|
+
css(image, { visibility: 'hidden' });
|
|
610
|
+
|
|
611
|
+
// hide snap view if open
|
|
612
|
+
this.hideSnapView();
|
|
613
|
+
|
|
614
|
+
const onImageLoad = () => {
|
|
615
|
+
// hide the iv loader
|
|
616
|
+
css(ivLoader, { display: 'none' });
|
|
617
|
+
|
|
618
|
+
// show the image
|
|
619
|
+
css(image, { visibility: 'visible' });
|
|
620
|
+
|
|
621
|
+
// load high resolution image if provided
|
|
622
|
+
if (hiResImageSrc) {
|
|
623
|
+
this._loadHighResImage(hiResImageSrc);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// set loaded flag to true
|
|
627
|
+
this._state.loaded = true;
|
|
628
|
+
|
|
629
|
+
// calculate the dimension
|
|
630
|
+
this._calculateDimensions();
|
|
631
|
+
|
|
632
|
+
// dispatch image load event
|
|
633
|
+
if (this._listeners.onImageLoaded) {
|
|
634
|
+
this._listeners.onImageLoaded(this._callbackData);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// reset the zoom
|
|
638
|
+
this.resetZoom();
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
if (imageLoaded(image)) {
|
|
642
|
+
onImageLoad();
|
|
643
|
+
} else {
|
|
644
|
+
if (typeof this._events.imageLoad === 'function') {
|
|
645
|
+
this._events.imageLoad();
|
|
646
|
+
}
|
|
647
|
+
this._events.imageLoad = assignEvent(image, 'load', onImageLoad);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
_loadHighResImage (hiResImageSrc) {
|
|
652
|
+
const { imageWrap, container } = this._elements;
|
|
653
|
+
|
|
654
|
+
const lowResImg = this._elements.image;
|
|
655
|
+
|
|
656
|
+
const hiResImage = createElement({
|
|
657
|
+
tagName: 'img',
|
|
658
|
+
className: 'iv-image iv-large-image',
|
|
659
|
+
src: hiResImageSrc,
|
|
660
|
+
parent: imageWrap,
|
|
661
|
+
style: lowResImg.style.cssText,
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// add all the style attributes from lowResImg to highResImg
|
|
665
|
+
hiResImage.style.cssText = lowResImg.style.cssText;
|
|
666
|
+
|
|
667
|
+
this._elements.image = container.querySelectorAll('.iv-image');
|
|
668
|
+
|
|
669
|
+
const onHighResImageLoad = () => {
|
|
670
|
+
// remove the low size image and set this image as default image
|
|
671
|
+
remove(lowResImg);
|
|
672
|
+
this._elements.image = hiResImage;
|
|
673
|
+
// this._calculateDimensions();
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
if (imageLoaded(hiResImage)) {
|
|
677
|
+
onHighResImageLoad();
|
|
678
|
+
} else {
|
|
679
|
+
if (typeof this._events.hiResImageLoad === 'function') {
|
|
680
|
+
this._events.hiResImageLoad();
|
|
681
|
+
}
|
|
682
|
+
this._events.hiResImageLoad = assignEvent(hiResImage, 'load', onHighResImageLoad);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
_calculateDimensions () {
|
|
687
|
+
const { image, container, snapView, snapImage, zoomHandle } = this._elements;
|
|
688
|
+
|
|
689
|
+
// calculate content width of image and snap image
|
|
690
|
+
const imageWidth = parseInt(css(image, 'width'), 10);
|
|
691
|
+
const imageHeight = parseInt(css(image, 'height'), 10);
|
|
692
|
+
|
|
693
|
+
const contWidth = parseInt(css(container, 'width'), 10);
|
|
694
|
+
const contHeight = parseInt(css(container, 'height'), 10);
|
|
695
|
+
|
|
696
|
+
const snapViewWidth = snapView.clientWidth;
|
|
697
|
+
const snapViewHeight = snapView.clientHeight;
|
|
698
|
+
|
|
699
|
+
// set the container dimension
|
|
700
|
+
this._state.containerDim = {
|
|
701
|
+
w: contWidth,
|
|
702
|
+
h: contHeight,
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
// set the image dimension
|
|
706
|
+
const ratio = imageWidth / imageHeight;
|
|
707
|
+
|
|
708
|
+
const imgWidth = (imageWidth > imageHeight && contHeight >= contWidth) || ratio * contHeight > contWidth
|
|
709
|
+
? contWidth
|
|
710
|
+
: ratio * contHeight;
|
|
711
|
+
|
|
712
|
+
const imgHeight = imgWidth / ratio;
|
|
713
|
+
|
|
714
|
+
this._state.imageDim = {
|
|
715
|
+
w: imgWidth,
|
|
716
|
+
h: imgHeight,
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// reset image position and zoom
|
|
720
|
+
css(image, {
|
|
721
|
+
width: `${imgWidth}px`,
|
|
722
|
+
height: `${imgHeight}px`,
|
|
723
|
+
left: `${(contWidth - imgWidth) / 2}px`,
|
|
724
|
+
top: `${(contHeight - imgHeight) / 2}px`,
|
|
725
|
+
maxWidth: 'none',
|
|
726
|
+
maxHeight: 'none',
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// set the snap Image dimension
|
|
730
|
+
const snapWidth = imgWidth > imgHeight ? snapViewWidth : imgWidth * snapViewHeight / imgHeight;
|
|
731
|
+
const snapHeight = imgHeight > imgWidth ? snapViewHeight : imgHeight * snapViewWidth / imgWidth;
|
|
732
|
+
|
|
733
|
+
this._state.snapImageDim = {
|
|
734
|
+
w: snapWidth,
|
|
735
|
+
h: snapHeight,
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
css(snapImage, {
|
|
739
|
+
width: `${snapWidth}px`,
|
|
740
|
+
height: `${snapHeight}px`,
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
const zoomSlider = snapView.querySelector('.iv-zoom-slider').clientWidth;
|
|
744
|
+
// calculate zoom slider area
|
|
745
|
+
this._state.zoomSliderLength = zoomSlider - zoomHandle.offsetWidth;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
resetZoom (animate = true) {
|
|
749
|
+
const { zoomValue } = this._options;
|
|
750
|
+
|
|
751
|
+
if (!animate) {
|
|
752
|
+
this._state.zoomValue = zoomValue;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
this.zoom(zoomValue);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
zoom = (perc, point) => {
|
|
759
|
+
const { _options, _elements, _state } = this;
|
|
760
|
+
const { zoomValue: curPerc, imageDim, containerDim, zoomSliderLength } = _state;
|
|
761
|
+
const { image, zoomHandle } = _elements;
|
|
762
|
+
const { maxZoom } = _options;
|
|
763
|
+
|
|
764
|
+
perc = Math.round(Math.max(100, perc));
|
|
765
|
+
perc = Math.min(maxZoom, perc);
|
|
766
|
+
|
|
767
|
+
point = point || {
|
|
768
|
+
x: containerDim.w / 2,
|
|
769
|
+
y: containerDim.h / 2,
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const curLeft = parseFloat(css(image, 'left'));
|
|
773
|
+
const curTop = parseFloat(css(image, 'top'));
|
|
774
|
+
|
|
775
|
+
// clear any panning frames
|
|
776
|
+
this._clearFrames();
|
|
777
|
+
|
|
778
|
+
let step = 0;
|
|
779
|
+
|
|
780
|
+
const baseLeft = (containerDim.w - imageDim.w) / 2;
|
|
781
|
+
const baseTop = (containerDim.h - imageDim.h) / 2;
|
|
782
|
+
const baseRight = containerDim.w - baseLeft;
|
|
783
|
+
const baseBottom = containerDim.h - baseTop;
|
|
784
|
+
|
|
785
|
+
const zoom = () => {
|
|
786
|
+
step++;
|
|
787
|
+
|
|
788
|
+
if (step < 16) {
|
|
789
|
+
this._frames.zoomFrame = requestAnimationFrame(zoom);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
let tickZoom = easeOutQuart(step, curPerc, perc - curPerc, 16);
|
|
793
|
+
// snap in at the last percent to more often land at the exact value
|
|
794
|
+
// only do that at the target percent value to make the animation as smooth as possible
|
|
795
|
+
if (Math.abs(perc - tickZoom) < 1) {
|
|
796
|
+
tickZoom = perc;
|
|
797
|
+
}
|
|
798
|
+
const ratio = tickZoom / curPerc;
|
|
799
|
+
|
|
800
|
+
const imgWidth = imageDim.w * tickZoom / 100;
|
|
801
|
+
const imgHeight = imageDim.h * tickZoom / 100;
|
|
802
|
+
|
|
803
|
+
let newLeft = -((point.x - curLeft) * ratio - point.x);
|
|
804
|
+
let newTop = -((point.y - curTop) * ratio - point.y);
|
|
805
|
+
|
|
806
|
+
// fix for left and top
|
|
807
|
+
newLeft = Math.min(newLeft, baseLeft);
|
|
808
|
+
newTop = Math.min(newTop, baseTop);
|
|
809
|
+
|
|
810
|
+
// fix for right and bottom
|
|
811
|
+
if (newLeft + imgWidth < baseRight) {
|
|
812
|
+
newLeft = baseRight - imgWidth; // newLeft - (newLeft + imgWidth - baseRight)
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (newTop + imgHeight < baseBottom) {
|
|
816
|
+
newTop = baseBottom - imgHeight; // newTop + (newTop + imgHeight - baseBottom)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
css(image, {
|
|
820
|
+
height: `${imgHeight}px`,
|
|
821
|
+
width: `${imgWidth}px`,
|
|
822
|
+
left: `${newLeft}px`,
|
|
823
|
+
top: `${newTop}px`,
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
this._state.zoomValue = tickZoom;
|
|
827
|
+
|
|
828
|
+
this._resizeSnapHandle(imgWidth, imgHeight, newLeft, newTop);
|
|
829
|
+
|
|
830
|
+
// update zoom handle position
|
|
831
|
+
css(zoomHandle, {
|
|
832
|
+
left: `${(tickZoom - 100) * zoomSliderLength / (maxZoom - 100)}px`,
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// dispatch zoom changed event
|
|
836
|
+
if (this._listeners.onZoomChange) {
|
|
837
|
+
this._listeners.onZoomChange(this._callbackData);
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
zoom();
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
_clearFrames = () => {
|
|
845
|
+
const { slideMomentumCheck, sliderMomentumFrame, zoomFrame } = this._frames;
|
|
846
|
+
clearInterval(slideMomentumCheck);
|
|
847
|
+
cancelAnimationFrame(sliderMomentumFrame);
|
|
848
|
+
cancelAnimationFrame(zoomFrame);
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
_resizeSnapHandle = (imgWidth, imgHeight, imgLeft, imgTop) => {
|
|
852
|
+
const { _elements, _state } = this;
|
|
853
|
+
const { snapHandle, image } = _elements;
|
|
854
|
+
const { imageDim, containerDim, zoomValue, snapImageDim } = _state;
|
|
855
|
+
|
|
856
|
+
const imageWidth = imgWidth || imageDim.w * zoomValue / 100;
|
|
857
|
+
const imageHeight = imgHeight || imageDim.h * zoomValue / 100;
|
|
858
|
+
const imageLeft = imgLeft || parseFloat(css(image, 'left'));
|
|
859
|
+
const imageTop = imgTop || parseFloat(css(image, 'top'));
|
|
860
|
+
|
|
861
|
+
const left = -imageLeft * snapImageDim.w / imageWidth;
|
|
862
|
+
const top = -imageTop * snapImageDim.h / imageHeight;
|
|
863
|
+
|
|
864
|
+
const handleWidth = (containerDim.w * snapImageDim.w) / imageWidth;
|
|
865
|
+
const handleHeight = (containerDim.h * snapImageDim.h) / imageHeight;
|
|
866
|
+
|
|
867
|
+
css(snapHandle, {
|
|
868
|
+
top: `${top}px`,
|
|
869
|
+
left: `${left}px`,
|
|
870
|
+
width: `${handleWidth}px`,
|
|
871
|
+
height: `${handleHeight}px`,
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
this._state.snapHandleDim = {
|
|
875
|
+
w: handleWidth,
|
|
876
|
+
h: handleHeight,
|
|
877
|
+
};
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
showSnapView = (noTimeout) => {
|
|
881
|
+
const { snapViewVisible, zoomValue, loaded } = this._state;
|
|
882
|
+
const { snapView } = this._elements;
|
|
883
|
+
|
|
884
|
+
if (!this._options.snapView) return;
|
|
885
|
+
|
|
886
|
+
if (snapViewVisible || zoomValue <= 100 || !loaded) return;
|
|
887
|
+
|
|
888
|
+
clearTimeout(this._frames.snapViewTimeout);
|
|
889
|
+
|
|
890
|
+
this._state.snapViewVisible = true;
|
|
891
|
+
|
|
892
|
+
css(snapView, { opacity: 1, pointerEvents: 'inherit' });
|
|
893
|
+
|
|
894
|
+
if (!noTimeout) {
|
|
895
|
+
this._frames.snapViewTimeout = setTimeout(this.hideSnapView, 1500);
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
hideSnapView = () => {
|
|
900
|
+
const { snapView } = this._elements;
|
|
901
|
+
css(snapView, { opacity: 0, pointerEvents: 'none' });
|
|
902
|
+
this._state.snapViewVisible = false;
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
refresh = (animate = true) => {
|
|
906
|
+
this._calculateDimensions();
|
|
907
|
+
this.resetZoom(animate);
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
load (imageSrc, hiResImageSrc) {
|
|
911
|
+
this._images = {
|
|
912
|
+
imageSrc,
|
|
913
|
+
hiResImageSrc,
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
this._loadImages();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
destroy () {
|
|
920
|
+
const { container, domElement } = this._elements;
|
|
921
|
+
// destroy all the sliders
|
|
922
|
+
Object.entries(this._sliders).forEach(([, slider]) => {
|
|
923
|
+
slider.destroy();
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// unbind all events
|
|
927
|
+
Object.entries(this._events).forEach(([, unbindEvent]) => {
|
|
928
|
+
unbindEvent();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// clear all the frames
|
|
932
|
+
this._clearFrames();
|
|
933
|
+
|
|
934
|
+
// remove html from the container
|
|
935
|
+
remove(container.querySelector('.iv-wrap'));
|
|
936
|
+
|
|
937
|
+
// remove iv-container class from container
|
|
938
|
+
removeClass(container, 'iv-container');
|
|
939
|
+
|
|
940
|
+
// remove added style from container
|
|
941
|
+
removeCss(document.querySelector('html'), 'relative');
|
|
942
|
+
|
|
943
|
+
// if container has original image, unwrap the image and remove the class
|
|
944
|
+
// which will happen when domElement is not the container
|
|
945
|
+
if (domElement !== container) {
|
|
946
|
+
unwrap(domElement);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// remove imageViewer reference from dom element
|
|
950
|
+
domElement._imageViewer = null;
|
|
951
|
+
|
|
952
|
+
if (this._listeners.onDestroy) {
|
|
953
|
+
this._listeners.onDestroy();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Data will be passed to the callback registered with each new instance
|
|
959
|
+
*/
|
|
960
|
+
get _callbackData () {
|
|
961
|
+
return {
|
|
962
|
+
container: this._elements.container,
|
|
963
|
+
snapView: this._elements.snapView,
|
|
964
|
+
zoomValue: this._state.zoomValue,
|
|
965
|
+
reachedMin: Math.abs(this._state.zoomValue - 100) < 1,
|
|
966
|
+
reachedMax: Math.abs(this._state.zoomValue - this._options.maxZoom) < 1,
|
|
967
|
+
instance: this,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
ImageViewer.defaults = {
|
|
973
|
+
zoomValue: 100,
|
|
974
|
+
snapView: true,
|
|
975
|
+
maxZoom: 500,
|
|
976
|
+
refreshOnResize: true,
|
|
977
|
+
zoomOnMouseWheel: true,
|
|
978
|
+
hasZoomButtons: false,
|
|
979
|
+
zoomStep: 50,
|
|
980
|
+
listeners: {
|
|
981
|
+
onInit: null,
|
|
982
|
+
onDestroy: null,
|
|
983
|
+
onImageLoaded: null,
|
|
984
|
+
onZoomChange: null,
|
|
985
|
+
},
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
export default ImageViewer;
|