iv-viewer
Version:
A zooming and panning plugin inspired by google photos for your web images.
996 lines (772 loc) • 27.2 kB
JavaScript
import {
createElement,
addClass,
removeClass,
css,
removeCss,
wrap,
unwrap,
remove,
easeOutQuart,
imageLoaded,
clamp,
assignEvent,
getTouchPointsDistance,
preventDefault,
ZOOM_CONSTANT,
MOUSE_WHEEL_COUNT,
} from './util';
import Slider from './Slider';
class ImageViewer {
get zoomInButton () {
return this._options.hasZoomButtons ? `<div class="iv-button-zoom--in" role="button"></div>` : '';
}
get zoomOutButton () {
return this._options.hasZoomButtons ? `<div class="iv-button-zoom--out" role="button"></div>` : '';
}
get imageViewHtml () {
return `
<div class="iv-loader"></div>
<div class="iv-snap-view">
<div class="iv-snap-image-wrap">
<div class="iv-snap-handle"></div>
</div>
<div class="iv-zoom-actions ${this._options.hasZoomButtons ? 'iv-zoom-actions--has-buttons' : ''}">
${this.zoomInButton}
<div class="iv-zoom-slider">
<div class="iv-zoom-handle"></div>
</div>
${this.zoomOutButton}
</div>
</div>
<div class="iv-image-view" >
<div class="iv-image-wrap" ></div>
</div>
`;
}
constructor (element, options = {}) {
const { container, domElement, imageSrc, hiResImageSrc } = this._findContainerAndImageSrc(element, options);
// containers for elements
this._elements = {
container,
domElement,
};
this._options = { ...ImageViewer.defaults, ...options };
// container for all events
this._events = {
};
this._listeners = this._options.listeners || {};
// container for all timeout and frames
this._frames = {
};
// container for all sliders
this._sliders = {
};
// maintain current state
this._state = {
zoomValue: this._options.zoomValue,
};
this._images = {
imageSrc,
hiResImageSrc,
};
this._init();
if (imageSrc) {
this._loadImages();
}
// store reference of imageViewer in domElement
domElement._imageViewer = this;
}
_findContainerAndImageSrc (element) {
let domElement = element;
let imageSrc, hiResImageSrc;
if (typeof element === 'string') {
domElement = document.querySelector(element);
}
// throw error if imageViewer is already assigned
if (domElement._imageViewer) {
throw new Error('An image viewer is already being initiated on the element.');
}
let container = element;
if (domElement.tagName === 'IMG') {
imageSrc = domElement.src;
hiResImageSrc = domElement.getAttribute('high-res-src') || domElement.getAttribute('data-high-res-src');
// wrap the image with iv-container div
container = wrap(domElement, { className: 'iv-container iv-image-mode', style: { display: 'inline-block', overflow: 'hidden' } });
// hide the image and add iv-original-img class
css(domElement, {
opacity: 0,
position: 'relative',
zIndex: -1,
});
} else {
imageSrc = domElement.getAttribute('src') || domElement.getAttribute('data-src');
hiResImageSrc = domElement.getAttribute('high-res-src') || domElement.getAttribute('data-high-res-src');
}
return {
container,
domElement,
imageSrc,
hiResImageSrc,
};
}
_init () {
// initialize the dom elements
this._initDom();
// initialize slider
this._initImageSlider();
this._initSnapSlider();
this._initZoomSlider();
// enable pinch and zoom feature for touch screens
this._pinchAndZoom();
// enable scroll zoom interaction
this._scrollZoom();
// enable double tap to zoom interaction
this._doubleTapToZoom();
// initialize events
this._initEvents();
}
_initDom () {
const { container } = this._elements;
// add image-viewer layout elements
createElement({
tagName: 'div',
className: 'iv-wrap',
html: this.imageViewHtml,
parent: container,
});
// add container class on the container
addClass(container, 'iv-container');
// if the element is static position, position it relatively
if (css(container, 'position') === 'static') {
css(container, { position: 'relative' });
}
// save references for later use
this._elements = {
...this._elements,
snapView: container.querySelector('.iv-snap-view'),
snapImageWrap: container.querySelector('.iv-snap-image-wrap'),
imageWrap: container.querySelector('.iv-image-wrap'),
snapHandle: container.querySelector('.iv-snap-handle'),
zoomHandle: container.querySelector('.iv-zoom-handle'),
zoomIn: container.querySelector('.iv-button-zoom--in'),
zoomOut: container.querySelector('.iv-button-zoom--out'),
};
if (this._listeners.onInit) {
this._listeners.onInit(this._callbackData);
}
}
_initImageSlider () {
const {
_elements,
} = this;
const { imageWrap } = _elements;
let positions, currentPos;
/* Add slide interaction to image */
const imageSlider = new Slider(imageWrap, {
isSliderEnabled: () => {
const { loaded, zooming, zoomValue } = this._state;
return loaded && !zooming && zoomValue > 100;
},
onStart: (e, position) => {
const { snapSlider } = this._sliders;
// clear all animation frame and interval
this._clearFrames();
snapSlider.onStart();
// reset positions
positions = [position, position];
currentPos = undefined;
this._frames.slideMomentumCheck = setInterval(() => {
if (!currentPos) return;
positions.shift();
positions.push({
x: currentPos.mx,
y: currentPos.my,
});
}, 50);
},
onMove: (e, position) => {
const { snapImageDim } = this._state;
const { snapSlider } = this._sliders;
const imageCurrentDim = this._getImageCurrentDim();
currentPos = position;
snapSlider.onMove(e, {
dx: -position.dx * snapImageDim.w / imageCurrentDim.w,
dy: -position.dy * snapImageDim.h / imageCurrentDim.h,
});
},
onEnd: () => {
const { snapImageDim } = this._state;
const { snapSlider } = this._sliders;
const imageCurrentDim = this._getImageCurrentDim();
// clear all animation frame and interval
this._clearFrames();
let step, positionX, positionY;
const xDiff = positions[1].x - positions[0].x;
const yDiff = positions[1].y - positions[0].y;
const momentum = () => {
if (step <= 60) {
this._frames.sliderMomentumFrame = requestAnimationFrame(momentum);
}
positionX += easeOutQuart(step, xDiff / 3, -xDiff / 3, 60);
positionY += easeOutQuart(step, yDiff / 3, -yDiff / 3, 60);
snapSlider.onMove(null, {
dx: -(positionX * snapImageDim.w / imageCurrentDim.w),
dy: -(positionY * snapImageDim.h / imageCurrentDim.h),
});
step++;
};
if (Math.abs(xDiff) > 30 || Math.abs(yDiff) > 30) {
step = 1;
positionX = currentPos.dx;
positionY = currentPos.dy;
momentum();
}
},
});
imageSlider.init();
this._sliders.imageSlider = imageSlider;
}
_initSnapSlider () {
const {
snapHandle,
} = this._elements;
let startHandleTop, startHandleLeft;
const snapSlider = new Slider(snapHandle, {
isSliderEnabled: () => {
return this._state.loaded;
},
onStart: () => {
const { slideMomentumCheck, sliderMomentumFrame } = this._frames;
startHandleTop = parseFloat(css(snapHandle, 'top'));
startHandleLeft = parseFloat(css(snapHandle, 'left'));
// stop momentum on image
clearInterval(slideMomentumCheck);
cancelAnimationFrame(sliderMomentumFrame);
},
onMove: (e, position) => {
const { snapHandleDim, snapImageDim } = this._state;
const { image } = this._elements;
const imageCurrentDim = this._getImageCurrentDim();
// find handle left and top and make sure they lay between the snap image
const maxLeft = Math.max(snapImageDim.w - snapHandleDim.w, startHandleLeft);
const maxTop = Math.max(snapImageDim.h - snapHandleDim.h, startHandleTop);
const minLeft = Math.min(0, startHandleLeft);
const minTop = Math.min(0, startHandleTop);
let left = clamp(startHandleLeft + position.dx, minLeft, maxLeft);
let top = clamp(startHandleTop + position.dy, minTop, maxTop);
const imgLeft = -left * imageCurrentDim.w / snapImageDim.w;
const imgTop = -top * imageCurrentDim.h / snapImageDim.h;
css(snapHandle, {
left: `${left}px`,
top: `${top}px`,
});
css(image, {
left: `${imgLeft}px`,
top: `${imgTop}px`,
});
},
});
snapSlider.init();
this._sliders.snapSlider = snapSlider;
}
_initZoomSlider () {
const { snapView, zoomHandle } = this._elements;
// zoom in zoom out using zoom handle
const sliderElm = snapView.querySelector('.iv-zoom-slider');
let leftOffset, handleWidth;
// on zoom slider we have to follow the mouse and set the handle to its position.
const zoomSlider = new Slider(sliderElm, {
isSliderEnabled: () => {
return this._state.loaded;
},
onStart: (eStart) => {
const { zoomSlider: slider } = this._sliders;
leftOffset = sliderElm.getBoundingClientRect().left;
handleWidth = parseInt(css(zoomHandle, 'width'), 10);
// move the handle to current mouse position
slider.onMove(eStart);
},
onMove: (e) => {
const { maxZoom } = this._options;
const { zoomSliderLength } = this._state;
const clientX = e.clientX !== undefined ? e.clientX : e.touches[0].clientX;
const newLeft = clamp(clientX - leftOffset - handleWidth / 2, 0, zoomSliderLength);
const zoomValue = 100 + ((maxZoom - 100) * newLeft / zoomSliderLength);
this.zoom(zoomValue);
},
});
zoomSlider.init();
this._sliders.zoomSlider = zoomSlider;
}
_initEvents () {
this._snapViewEvents();
// handle window resize
if (this._options.refreshOnResize) {
this._events.onWindowResize = assignEvent(window, 'resize', this.refresh);
}
this._events.onDragStart = assignEvent(this._elements.container, 'dragstart', preventDefault);
}
_snapViewEvents () {
const { imageWrap, snapView } = this._elements;
// show snapView on mouse move
this._events.snapViewOnMouseMove = assignEvent(imageWrap, ['touchmove', 'mousemove'], () => {
this.showSnapView();
});
// keep showing snapView if on hover over it without any timeout
this._events.mouseEnterSnapView = assignEvent(snapView, ['mouseenter', 'touchstart'], () => {
this._state.snapViewVisible = false;
this.showSnapView(true);
});
// on mouse leave set timeout to hide snapView
this._events.mouseLeaveSnapView = assignEvent(snapView, ['mouseleave', 'touchend'], () => {
this._state.snapViewVisible = false;
this.showSnapView();
});
if (!this._options.hasZoomButtons) {
return;
}
const { zoomOut, zoomIn } = this._elements;
this._events.zoomInClick = assignEvent(zoomIn, ['click'], () => {
this.zoom(this._state.zoomValue + this._options.zoomStep || 50);
});
this._events.zoomOutClick = assignEvent(zoomOut, ['click'], () => {
this.zoom(this._state.zoomValue - this._options.zoomStep || 50);
});
}
_pinchAndZoom () {
const { imageWrap, container } = this._elements;
// apply pinch and zoom feature
const onPinchStart = (eStart) => {
const { loaded, zoomValue: startZoomValue } = this._state;
const { _events: events } = this;
if (!loaded) return;
const touch0 = eStart.touches[0];
const touch1 = eStart.touches[1];
if (!(touch0 && touch1)) {
return;
}
this._state.zooming = true;
const contOffset = container.getBoundingClientRect();
// find distance between two touch points
const startDist = getTouchPointsDistance(eStart.touches);
// find the center for the zoom
const center = {
x: (touch1.clientX + touch0.clientX) / 2 - contOffset.left,
y: (touch1.clientY + touch0.clientY) / 2 - contOffset.top,
};
const moveListener = (eMove) => {
// eMove.preventDefault();
const newDist = getTouchPointsDistance(eMove.touches);
const zoomValue = startZoomValue + (newDist - startDist) / 2;
this.zoom(zoomValue, center);
};
const endListener = (eEnd) => {
// unbind events
events.pinchMove();
events.pinchEnd();
this._state.zooming = false;
// properly resume move event if one finger remains
if (eEnd.touches.length === 1) {
this._sliders.imageSlider.startHandler(eEnd);
}
};
// remove events if already assigned
if (events.pinchMove) events.pinchMove();
if (events.pinchEnd) events.pinchEnd();
// assign events
events.pinchMove = assignEvent(document, 'touchmove', moveListener);
events.pinchEnd = assignEvent(document, 'touchend', endListener);
};
this._events.pinchStart = assignEvent(imageWrap, 'touchstart', onPinchStart);
}
_scrollZoom () {
/* Add zoom interaction in mouse wheel */
const { _options } = this;
const { container, imageWrap } = this._elements;
let changedDelta = 0;
const onMouseWheel = (e) => {
const { loaded, zoomValue } = this._state;
if (!_options.zoomOnMouseWheel || !loaded) return;
// clear all animation frame and interval
this._clearFrames();
// cross-browser wheel delta
const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail || -e.deltaY));
const newZoomValue = zoomValue * (100 + delta * ZOOM_CONSTANT) / 100;
if (!(newZoomValue >= 100 && newZoomValue <= _options.maxZoom)) {
changedDelta += Math.abs(delta);
} else {
changedDelta = 0;
}
e.preventDefault();
if (changedDelta > MOUSE_WHEEL_COUNT) return;
const contOffset = container.getBoundingClientRect();
const x = e.clientX - contOffset.left;
const y = e.clientY - contOffset.top;
this.zoom(newZoomValue, {
x,
y,
});
// show the snap viewer
this.showSnapView();
};
this._events.scrollZoom = assignEvent(imageWrap, 'wheel', onMouseWheel);
}
_doubleTapToZoom () {
const { imageWrap } = this._elements;
// handle double tap for zoom in and zoom out
let touchTime = 0;
let point;
const onDoubleTap = (e) => {
if (touchTime === 0) {
touchTime = Date.now();
point = {
x: e.clientX,
y: e.clientY,
};
} else if (Date.now() - touchTime < 500 && Math.abs(e.clientX - point.x) < 50 && Math.abs(e.clientY - point.y) < 50) {
if (this._state.zoomValue === this._options.zoomValue) {
this.zoom(200);
} else {
this.resetZoom();
}
touchTime = 0;
} else {
touchTime = 0;
}
};
this._events.doubleTapToZoom = assignEvent(imageWrap, 'click', onDoubleTap);
}
_getImageCurrentDim () {
const { zoomValue, imageDim } = this._state;
return {
w: imageDim.w * (zoomValue / 100),
h: imageDim.h * (zoomValue / 100),
};
}
_loadImages () {
const { _images, _elements } = this;
const { imageSrc, hiResImageSrc } = _images;
const { container, snapImageWrap, imageWrap } = _elements;
const ivLoader = container.querySelector('.iv-loader');
// remove old images
remove(container.querySelectorAll('.iv-snap-image, .iv-image'));
// add snapView image
const snapImage = createElement({
tagName: 'img',
className: 'iv-snap-image',
src: imageSrc,
insertBefore: snapImageWrap.firstChild,
parent: snapImageWrap,
});
// add image
const image = createElement({
tagName: 'img',
className: 'iv-image iv-small-image',
src: imageSrc,
parent: imageWrap,
});
this._state.loaded = false;
// store image reference in _elements
this._elements.image = image;
this._elements.snapImage = snapImage;
css(ivLoader, { display: 'block' });
// keep visibility hidden until image is loaded
css(image, { visibility: 'hidden' });
// hide snap view if open
this.hideSnapView();
const onImageLoad = () => {
// hide the iv loader
css(ivLoader, { display: 'none' });
// show the image
css(image, { visibility: 'visible' });
// load high resolution image if provided
if (hiResImageSrc) {
this._loadHighResImage(hiResImageSrc);
}
// set loaded flag to true
this._state.loaded = true;
// calculate the dimension
this._calculateDimensions();
// dispatch image load event
if (this._listeners.onImageLoaded) {
this._listeners.onImageLoaded(this._callbackData);
}
// reset the zoom
this.resetZoom();
};
if (imageLoaded(image)) {
onImageLoad();
} else {
if (typeof this._events.imageLoad == 'function') {
this._events.imageLoad()
}
this._events.imageLoad = assignEvent(image, 'load', onImageLoad);
}
}
_loadHighResImage (hiResImageSrc) {
const { imageWrap, container } = this._elements;
const lowResImg = this._elements.image;
const hiResImage = createElement({
tagName: 'img',
className: 'iv-image iv-large-image',
src: hiResImageSrc,
parent: imageWrap,
style: lowResImg.style.cssText,
});
// add all the style attributes from lowResImg to highResImg
hiResImage.style.cssText = lowResImg.style.cssText;
this._elements.image = container.querySelectorAll('.iv-image');
const onHighResImageLoad = () => {
// remove the low size image and set this image as default image
remove(lowResImg);
this._elements.image = hiResImage;
// this._calculateDimensions();
};
if (imageLoaded(hiResImage)) {
onHighResImageLoad();
} else {
if (typeof this._events.hiResImageLoad == 'function') {
this._events.hiResImageLoad()
}
this._events.hiResImageLoad = assignEvent(hiResImage, 'load', onHighResImageLoad);
}
}
_calculateDimensions () {
const { image, container, snapView, snapImage, zoomHandle } = this._elements;
// calculate content width of image and snap image
const imageWidth = parseInt(css(image, 'width'), 10);
const imageHeight = parseInt(css(image, 'height'), 10);
const contWidth = parseInt(css(container, 'width'), 10);
const contHeight = parseInt(css(container, 'height'), 10);
const snapViewWidth = snapView.clientWidth;
const snapViewHeight = snapView.clientHeight;
// set the container dimension
this._state.containerDim = {
w: contWidth,
h: contHeight,
};
// set the image dimension
let imgWidth;
let imgHeight;
const ratio = imageWidth / imageHeight;
imgWidth = (imageWidth > imageHeight && contHeight >= contWidth) || ratio * contHeight > contWidth
? contWidth
: ratio * contHeight;
imgHeight = imgWidth / ratio;
this._state.imageDim = {
w: imgWidth,
h: imgHeight,
};
// reset image position and zoom
css(image, {
width: `${imgWidth}px`,
height: `${imgHeight}px`,
left: `${(contWidth - imgWidth) / 2}px`,
top: `${(contHeight - imgHeight) / 2}px`,
maxWidth: 'none',
maxHeight: 'none',
});
// set the snap Image dimension
const snapWidth = imgWidth > imgHeight ? snapViewWidth : imgWidth * snapViewHeight / imgHeight;
const snapHeight = imgHeight > imgWidth ? snapViewHeight : imgHeight * snapViewWidth / imgWidth;
this._state.snapImageDim = {
w: snapWidth,
h: snapHeight,
};
css(snapImage, {
width: `${snapWidth}px`,
height: `${snapHeight}px`,
});
const zoomSlider = snapView.querySelector('.iv-zoom-slider').clientWidth;
// calculate zoom slider area
this._state.zoomSliderLength = zoomSlider - zoomHandle.offsetWidth;
}
resetZoom (animate = true) {
const { zoomValue } = this._options;
if (!animate) {
this._state.zoomValue = zoomValue;
}
this.zoom(zoomValue);
}
zoom = (perc, point) => {
const { _options, _elements, _state } = this;
const { zoomValue: curPerc, imageDim, containerDim, zoomSliderLength } = _state;
const { image, zoomHandle } = _elements;
const { maxZoom } = _options;
perc = Math.round(Math.max(100, perc));
perc = Math.min(maxZoom, perc);
point = point || {
x: containerDim.w / 2,
y: containerDim.h / 2,
};
const curLeft = parseFloat(css(image, 'left'));
const curTop = parseFloat(css(image, 'top'));
// clear any panning frames
this._clearFrames();
let step = 0;
const baseLeft = (containerDim.w - imageDim.w) / 2;
const baseTop = (containerDim.h - imageDim.h) / 2;
const baseRight = containerDim.w - baseLeft;
const baseBottom = containerDim.h - baseTop;
const zoom = () => {
step++;
if (step < 16) {
this._frames.zoomFrame = requestAnimationFrame(zoom);
}
let tickZoom = easeOutQuart(step, curPerc, perc - curPerc, 16);
// snap in at the last percent to more often land at the exact value
// only do that at the target percent value to make the animation as smooth as possible
if (Math.abs(perc - tickZoom) < 1) {
tickZoom = perc
}
const ratio = tickZoom / curPerc;
const imgWidth = imageDim.w * tickZoom / 100;
const imgHeight = imageDim.h * tickZoom / 100;
let newLeft = -((point.x - curLeft) * ratio - point.x);
let newTop = -((point.y - curTop) * ratio - point.y);
// fix for left and top
newLeft = Math.min(newLeft, baseLeft);
newTop = Math.min(newTop, baseTop);
// fix for right and bottom
if (newLeft + imgWidth < baseRight) {
newLeft = baseRight - imgWidth; // newLeft - (newLeft + imgWidth - baseRight)
}
if (newTop + imgHeight < baseBottom) {
newTop = baseBottom - imgHeight; // newTop + (newTop + imgHeight - baseBottom)
}
css(image, {
height: `${imgHeight}px`,
width: `${imgWidth}px`,
left: `${newLeft}px`,
top: `${newTop}px`,
});
this._state.zoomValue = tickZoom;
this._resizeSnapHandle(imgWidth, imgHeight, newLeft, newTop);
// update zoom handle position
css(zoomHandle, {
left: `${(tickZoom - 100) * zoomSliderLength / (maxZoom - 100)}px`,
});
// dispatch zoom changed event
if (this._listeners.onZoomChange) {
this._listeners.onZoomChange(this._callbackData);
}
};
zoom();
}
_clearFrames = () => {
const { slideMomentumCheck, sliderMomentumFrame, zoomFrame } = this._frames;
clearInterval(slideMomentumCheck);
cancelAnimationFrame(sliderMomentumFrame);
cancelAnimationFrame(zoomFrame);
}
_resizeSnapHandle = (imgWidth, imgHeight, imgLeft, imgTop) => {
const { _elements, _state } = this;
const { snapHandle, image } = _elements;
const { imageDim, containerDim, zoomValue, snapImageDim } = _state;
const imageWidth = imgWidth || imageDim.w * zoomValue / 100;
const imageHeight = imgHeight || imageDim.h * zoomValue / 100;
const imageLeft = imgLeft || parseFloat(css(image, 'left'));
const imageTop = imgTop || parseFloat(css(image, 'top'));
const left = -imageLeft * snapImageDim.w / imageWidth;
const top = -imageTop * snapImageDim.h / imageHeight;
const handleWidth = (containerDim.w * snapImageDim.w) / imageWidth;
const handleHeight = (containerDim.h * snapImageDim.h) / imageHeight;
css(snapHandle, {
top: `${top}px`,
left: `${left}px`,
width: `${handleWidth}px`,
height: `${handleHeight}px`,
});
this._state.snapHandleDim = {
w: handleWidth,
h: handleHeight,
};
}
showSnapView = (noTimeout) => {
const { snapViewVisible, zoomValue, loaded } = this._state;
const { snapView } = this._elements;
if (!this._options.snapView) return;
if (snapViewVisible || zoomValue <= 100 || !loaded) return;
clearTimeout(this._frames.snapViewTimeout);
this._state.snapViewVisible = true;
css(snapView, { opacity: 1, pointerEvents: 'inherit' });
if (!noTimeout) {
this._frames.snapViewTimeout = setTimeout(this.hideSnapView, 1500);
}
}
hideSnapView = () => {
const { snapView } = this._elements;
css(snapView, { opacity: 0, pointerEvents: 'none' });
this._state.snapViewVisible = false;
}
refresh = (animate = true) => {
this._calculateDimensions();
this.resetZoom(animate);
}
load (imageSrc, hiResImageSrc) {
this._images = {
imageSrc,
hiResImageSrc,
};
this._loadImages();
}
destroy () {
const { container, domElement } = this._elements;
// destroy all the sliders
Object.entries(this._sliders).forEach(([key, slider]) => {
slider.destroy();
});
// unbind all events
Object.entries(this._events).forEach(([key, unbindEvent]) => {
unbindEvent();
});
// clear all the frames
this._clearFrames();
// remove html from the container
remove(container.querySelector('.iv-wrap'));
// remove iv-container class from container
removeClass(container, 'iv-container');
// remove added style from container
removeCss(document.querySelector('html'), 'relative');
// if container has original image, unwrap the image and remove the class
// which will happen when domElement is not the container
if (domElement !== container) {
unwrap(domElement);
}
// remove imageViewer reference from dom element
domElement._imageViewer = null;
if (this._listeners.onDestroy) {
this._listeners.onDestroy();
}
}
/**
* Data will be passed to the callback registered with each new instance
*/
get _callbackData () {
return {
container: this._elements.container,
snapView: this._elements.snapView,
zoomValue: this._state.zoomValue,
reachedMin: Math.abs(this._state.zoomValue - 100) < 1,
reachedMax: Math.abs(this._state.zoomValue - this._options.maxZoom) < 1,
instance: this,
};
}
}
ImageViewer.defaults = {
zoomValue: 100,
snapView: true,
maxZoom: 500,
refreshOnResize: true,
zoomOnMouseWheel: true,
hasZoomButtons: false,
zoomStep: 50,
listeners: {
onInit: null,
onDestroy: null,
onImageLoaded: null,
onZoomChange: null,
},
};
export default ImageViewer;