- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined'
- ? (module.exports = factory())
- : typeof define === 'function' && define.amd
- ? define(factory)
- : ((global =
- typeof globalThis !== 'undefined' ? globalThis : global || self),
- (global.WZoom = factory()));
- })(this, function () {
- 'use strict';
-
- /**
- * Get element position (with support old browsers)
- * @param {Element} element
- * @returns {{top: number, left: number}}
- */
- function getElementPosition(element) {
- var box = element.getBoundingClientRect();
- var _document = document,
- body = _document.body,
- documentElement = _document.documentElement;
- var scrollTop =
- window.pageYOffset || documentElement.scrollTop || body.scrollTop;
- var scrollLeft =
- window.pageXOffset || documentElement.scrollLeft || body.scrollLeft;
- var clientTop = documentElement.clientTop || body.clientTop || 0;
- var clientLeft = documentElement.clientLeft || body.clientLeft || 0;
- var top = box.top + scrollTop - clientTop;
- var left = box.left + scrollLeft - clientLeft;
- return {
- top: top,
- left: left,
- };
- }
- /**
- * Universal alternative to Object.assign()
- * @param {Object} destination
- * @param {Object} source
- * @returns {Object}
- */
-
- function extendObject(destination, source) {
- if (destination && source) {
- for (var key in source) {
- if (source.hasOwnProperty(key)) {
- destination[key] = source[key];
- }
- }
- }
-
- return destination;
- }
- /**
- * @param target
- * @param type
- * @param listener
- * @param options
- */
-
- function on(target, type, listener) {
- var options =
- arguments.length > 3 && arguments[3] !== undefined
- ? arguments[3]
- : false;
- target.addEventListener(type, listener, options);
- }
- /**
- * @param target
- * @param type
- * @param listener
- * @param options
- */
-
- function off(target, type, listener) {
- var options =
- arguments.length > 3 && arguments[3] !== undefined
- ? arguments[3]
- : false;
- target.removeEventListener(type, listener, options);
- }
- function isTouch() {
- return (
- 'ontouchstart' in window ||
- navigator.MaxTouchPoints > 0 ||
- navigator.msMaxTouchPoints > 0
- );
- }
- function eventClientX(event) {
- return event.type === 'wheel' ||
- event.type === 'mousedown' ||
- event.type === 'mousemove' ||
- event.type === 'mouseup'
- ? event.clientX
- : event.changedTouches[0].clientX;
- }
- function eventClientY(event) {
- return event.type === 'wheel' ||
- event.type === 'mousedown' ||
- event.type === 'mousemove' ||
- event.type === 'mouseup'
- ? event.clientY
- : event.changedTouches[0].clientY;
- }
-
- /**
- * @class DragScrollable
- * @param {Object} windowObject
- * @param {Object} contentObject
- * @param {Object} options
- * @constructor
- */
-
- function DragScrollable(windowObject, contentObject) {
- var options =
- arguments.length > 2 && arguments[2] !== undefined
- ? arguments[2]
- : {};
- this._dropHandler = this._dropHandler.bind(this);
- this._grabHandler = this._grabHandler.bind(this);
- this._moveHandler = this._moveHandler.bind(this);
- this.options = extendObject(
- {
- // smooth extinction moving element after set loose
- smoothExtinction: false,
- // callback triggered when grabbing an element
- onGrab: null,
- // callback triggered when moving an element
- onMove: null,
- // callback triggered when dropping an element
- onDrop: null,
- },
- options
- ); // check if we're using a touch screen
-
- this.isTouch = isTouch(); // switch to touch events if using a touch screen
-
- this.events = this.isTouch
- ? {
- grab: 'touchstart',
- move: 'touchmove',
- drop: 'touchend',
- }
- : {
- grab: 'mousedown',
- move: 'mousemove',
- drop: 'mouseup',
- }; // for the touch screen we set the parameter forcibly
-
- this.events.options = this.isTouch
- ? {
- passive: false,
- }
- : false;
- this.window = windowObject;
- this.content = contentObject;
- on(
- this.content.$element,
- this.events.grab,
- this._grabHandler,
- this.events.options
- );
- }
-
- DragScrollable.prototype = {
- constructor: DragScrollable,
- window: null,
- content: null,
- isTouch: false,
- isGrab: false,
- events: null,
- moveTimer: null,
- options: {},
- coordinates: null,
- speed: null,
- _grabHandler: function _grabHandler(event) {
- // if touch started (only one finger) or pressed left mouse button
- if (
- (this.isTouch && event.touches.length === 1) ||
- event.buttons === 1
- ) {
- event.preventDefault();
- this.isGrab = true;
- this.coordinates = {
- left: eventClientX(event),
- top: eventClientY(event),
- };
- this.speed = {
- x: 0,
- y: 0,
- };
- on(
- document,
- this.events.drop,
- this._dropHandler,
- this.events.options
- );
- on(
- document,
- this.events.move,
- this._moveHandler,
- this.events.options
- );
-
- if (typeof this.options.onGrab === 'function') {
- this.options.onGrab();
- }
- }
- },
- _dropHandler: function _dropHandler(event) {
- event.preventDefault();
- this.isGrab = false; // if (this.options.smoothExtinction) {
- // _moveExtinction.call(this, 'scrollLeft', numberExtinction(this.speed.x));
- // _moveExtinction.call(this, 'scrollTop', numberExtinction(this.speed.y));
- // }
-
- off(document, this.events.drop, this._dropHandler);
- off(document, this.events.move, this._moveHandler);
-
- if (typeof this.options.onDrop === 'function') {
- this.options.onDrop();
- }
- },
- _moveHandler: function _moveHandler(event) {
- if (this.isTouch && event.touches.length > 1) return false;
- event.preventDefault();
- var window = this.window,
- content = this.content,
- speed = this.speed,
- coordinates = this.coordinates,
- options = this.options; // speed of change of the coordinate of the mouse cursor along the X/Y axis
-
- speed.x = eventClientX(event) - coordinates.left;
- speed.y = eventClientY(event) - coordinates.top;
- clearTimeout(this.moveTimer); // reset speed data if cursor stops
-
- this.moveTimer = setTimeout(function () {
- speed.x = 0;
- speed.y = 0;
- }, 50);
- var contentNewLeft = content.currentLeft + speed.x;
- var contentNewTop = content.currentTop + speed.y;
- var maxAvailableLeft =
- (content.currentWidth - window.originalWidth) / 2 +
- content.correctX;
- var maxAvailableTop =
- (content.currentHeight - window.originalHeight) / 2 +
- content.correctY; // if we do not go beyond the permissible boundaries of the window
-
- if (Math.abs(contentNewLeft) <= maxAvailableLeft)
- content.currentLeft = contentNewLeft; // if we do not go beyond the permissible boundaries of the window
-
- if (Math.abs(contentNewTop) <= maxAvailableTop)
- content.currentTop = contentNewTop;
-
- _transform(content.$element, {
- left: content.currentLeft,
- top: content.currentTop,
- scale: content.currentScale,
- });
-
- coordinates.left = eventClientX(event);
- coordinates.top = eventClientY(event);
-
- if (typeof options.onMove === 'function') {
- options.onMove();
- }
- },
- destroy: function destroy() {
- off(
- this.content.$element,
- this.events.grab,
- this._grabHandler,
- this.events.options
- );
-
- for (var key in this) {
- if (this.hasOwnProperty(key)) {
- this[key] = null;
- }
- }
- },
- };
-
- function _transform($element, _ref) {
- var left = _ref.left,
- top = _ref.top,
- scale = _ref.scale;
- $element.style.transform = 'translate3d('
- .concat(left, 'px, ')
- .concat(top, 'px, 0px) scale(')
- .concat(scale, ')');
- } // function _moveExtinction(field, speedArray) {
-
- /**
- * @class WZoom
- * @param {string|HTMLElement} selectorOrHTMLElement
- * @param {Object} options
- * @constructor
- */
-
- function WZoom(selectorOrHTMLElement) {
- var options =
- arguments.length > 1 && arguments[1] !== undefined
- ? arguments[1]
- : {};
- this._init = this._init.bind(this);
- this._prepare = this._prepare.bind(this);
- this._computeNewScale = this._computeNewScale.bind(this);
- this._computeNewPosition = this._computeNewPosition.bind(this);
- this._transform = this._transform.bind(this);
- this._wheelHandler = _wheelHandler.bind(this);
- this._downHandler = _downHandler.bind(this);
- this._upHandler = _upHandler.bind(this);
- this._zoomTwoFingers_TouchmoveHandler = _zoomTwoFingers_TouchmoveHandler.bind(
- this
- );
- this._zoomTwoFingers_TouchendHandler = _zoomTwoFingers_TouchendHandler.bind(
- this
- );
- /********************/
-
- /********************/
-
- this.content = {};
- this.window = {};
- this.isTouch = false;
- this.events = null;
- this.direction = 1;
- this.options = null;
- this.dragScrollable = null; // processing of the event "max / min zoom" begin only if there was really just a click
- // so as not to interfere with the DragScrollable module
-
- this.clickExpired = true;
- /********************/
-
- /********************/
-
- var defaults = {
- // type content: `image` - only one image, `html` - any HTML content
- type: 'image',
- // for type `image` computed auto (if width set null), for type `html` need set real html content width, else computed auto
- width: null,
- // for type `image` computed auto (if height set null), for type `html` need set real html content height, else computed auto
- height: null,
- // drag scrollable content
- dragScrollable: true,
- // options for the DragScrollable module
- dragScrollableOptions: {},
- // minimum allowed proportion of scale
- minScale: null,
- // maximum allowed proportion of scale
- maxScale: 1,
- // content resizing speed
- speed: 50,
- // zoom to maximum (minimum) size on click
- zoomOnClick: true,
- // if is true, then when the source image changes, the plugin will automatically restart init function (used with type = image)
- // attention: if false, it will work correctly only if the images are of the same size
- watchImageChange: true,
- };
-
- if (typeof selectorOrHTMLElement === 'string') {
- this.content.$element = document.querySelector(
- selectorOrHTMLElement
- );
- } else if (selectorOrHTMLElement instanceof HTMLElement) {
- this.content.$element = selectorOrHTMLElement;
- } else {
- throw 'WZoom: `selectorOrHTMLElement` must be selector or HTMLElement, and not '.concat(
- {}.toString.call(selectorOrHTMLElement)
- );
- } // check if we're using a touch screen
-
- this.isTouch = isTouch(); // switch to touch events if using a touch screen
-
- this.events = this.isTouch
- ? {
- down: 'touchstart',
- up: 'touchend',
- }
- : {
- down: 'mousedown',
- up: 'mouseup',
- }; // if using touch screen tells the browser that the default action will not be undone
-
- this.events.options = this.isTouch
- ? {
- passive: true,
- }
- : false;
-
- if (this.content.$element) {
- this.options = extendObject(defaults, options);
-
- if (
- this.options.minScale &&
- this.options.minScale >= this.options.maxScale
- ) {
- this.options.minScale = null;
- } // for window take just the parent
-
- this.window.$element = this.content.$element.parentNode;
-
- if (this.options.type === 'image') {
- var initAlreadyDone = false; // if the `image` has already been loaded
-
- if (this.content.$element.complete) {
- this._init();
-
- initAlreadyDone = true;
- }
-
- if (
- !initAlreadyDone ||
- this.options.watchImageChange === true
- ) {
- // even if the `image` has already been loaded (for "hotswap" of src support)
- on(
- this.content.$element,
- 'load',
- this._init, // if watchImageChange == false listen add only until the first call
- this.options.watchImageChange
- ? false
- : {
- once: true,
- }
- );
- }
- } else {
- this._init();
- }
- }
- }
-
- WZoom.prototype = {
- constructor: WZoom,
- _init: function _init() {
- this._prepare(); // support for zoom and pinch on touch screen devices
-
- if (this.isTouch) {
- this.fingersHypot = null;
- this.zoomPinchWasDetected = false;
- on(
- this.content.$element,
- 'touchmove',
- this._zoomTwoFingers_TouchmoveHandler
- );
- on(
- this.content.$element,
- 'touchend',
- this._zoomTwoFingers_TouchendHandler
- );
- }
-
- if (this.options.dragScrollable === true) {
- // this can happen if the src of this.content.$element (when type = image) is changed and repeat event load at image
- if (this.dragScrollable) {
- this.dragScrollable.destroy();
- }
-
- this.dragScrollable = new DragScrollable(
- this.window,
- this.content,
- this.options.dragScrollableOptions
- );
- }
-
- on(this.content.$element, 'wheel', this._wheelHandler);
-
- if (this.options.zoomOnClick) {
- on(
- this.content.$element,
- this.events.down,
- this._downHandler,
- this.events.options
- );
- on(
- this.content.$element,
- this.events.up,
- this._upHandler,
- this.events.options
- );
- }
- },
- _prepare: function _prepare() {
- var windowPosition = getElementPosition(this.window.$element); // original window sizes and position
-
- this.window.originalWidth = this.window.$element.offsetWidth;
- this.window.originalHeight = this.window.$element.offsetHeight;
- this.window.positionLeft = windowPosition.left;
- this.window.positionTop = windowPosition.top; // original content sizes
-
- if (this.options.type === 'image') {
- this.content.originalWidth =
- this.options.width || this.content.$element.naturalWidth;
- this.content.originalHeight =
- this.options.height || this.content.$element.naturalHeight;
- } else {
- this.content.originalWidth =
- this.options.width || this.content.$element.offsetWidth;
- this.content.originalHeight =
- this.options.height || this.content.$element.offsetHeight;
- } // minScale && maxScale
-
- this.content.minScale =
- this.options.minScale ||
- Math.min(
- this.window.originalWidth / this.content.originalWidth,
- this.window.originalHeight / this.content.originalHeight
- );
- this.content.maxScale = this.options.maxScale; // current content sizes and transform data
-
- this.content.currentWidth =
- this.content.originalWidth * this.content.minScale;
- this.content.currentHeight =
- this.content.originalHeight * this.content.minScale;
- this.content.currentLeft = 0;
- this.content.currentTop = 0;
- this.content.currentScale = this.content.minScale; // calculate indent-left and indent-top to of content from window borders
-
- this.content.correctX = Math.max(
- 0,
- (this.window.originalWidth - this.content.currentWidth) / 2
- );
- this.content.correctY = Math.max(
- 0,
- (this.window.originalHeight - this.content.currentHeight) / 2
- );
- this.content.$element.style.transform = 'translate3d(0px, 0px, 0px) scale('.concat(
- this.content.minScale,
- ')'
- );
-
- if (typeof this.options.prepare === 'function') {
- this.options.prepare();
- }
- },
- _computeNewScale: function _computeNewScale(delta) {
- this.direction = delta < 0 ? 1 : -1;
- var _this$content = this.content,
- minScale = _this$content.minScale,
- maxScale = _this$content.maxScale,
- currentScale = _this$content.currentScale;
- var contentNewScale =
- currentScale + this.direction / this.options.speed;
-
- if (contentNewScale < minScale) {
- this.direction = 1;
- } else if (contentNewScale > maxScale) {
- this.direction = -1;
- }
-
- return contentNewScale < minScale
- ? minScale
- : contentNewScale > maxScale
- ? maxScale
- : contentNewScale;
- },
- _computeNewPosition: function _computeNewPosition(
- contentNewScale,
- _ref
- ) {
- var x = _ref.x,
- y = _ref.y;
- var window = this.window,
- content = this.content;
- var contentNewWidth = content.originalWidth * contentNewScale;
- var contentNewHeight = content.originalHeight * contentNewScale;
- var _document = document,
- body = _document.body,
- documentElement = _document.documentElement;
- var scrollLeft =
- window.pageXOffset ||
- documentElement.scrollLeft ||
- body.scrollLeft;
- var scrollTop =
- window.pageYOffset ||
- documentElement.scrollTop ||
- body.scrollTop; // calculate the parameters along the X axis
-
- var leftWindowShiftX = x + scrollLeft - window.positionLeft;
- var centerWindowShiftX =
- window.originalWidth / 2 - leftWindowShiftX;
- var centerContentShiftX = centerWindowShiftX + content.currentLeft;
- var contentNewLeft =
- centerContentShiftX * (contentNewWidth / content.currentWidth) -
- centerContentShiftX +
- content.currentLeft; // check that the content does not go beyond the X axis
-
- if (
- this.direction === -1 &&
- (contentNewWidth - window.originalWidth) / 2 +
- content.correctX <
- Math.abs(contentNewLeft)
- ) {
- var positive = contentNewLeft < 0 ? -1 : 1;
- contentNewLeft =
- ((contentNewWidth - window.originalWidth) / 2 +
- content.correctX) *
- positive;
- } // calculate the parameters along the Y axis
-
- var topWindowShiftY = y + scrollTop - window.positionTop;
- var centerWindowShiftY =
- window.originalHeight / 2 - topWindowShiftY;
- var centerContentShiftY = centerWindowShiftY + content.currentTop;
- var contentNewTop =
- centerContentShiftY *
- (contentNewHeight / content.currentHeight) -
- centerContentShiftY +
- content.currentTop; // check that the content does not go beyond the Y axis
-
- if (
- this.direction === -1 &&
- (contentNewHeight - window.originalHeight) / 2 +
- content.correctY <
- Math.abs(contentNewTop)
- ) {
- var _positive = contentNewTop < 0 ? -1 : 1;
-
- contentNewTop =
- ((contentNewHeight - window.originalHeight) / 2 +
- content.correctY) *
- _positive;
- }
-
- if (contentNewScale === this.content.minScale) {
- contentNewLeft = contentNewTop = 0;
- }
-
- var response = {
- currentLeft: content.currentLeft,
- newLeft: contentNewLeft,
- currentTop: content.currentTop,
- newTop: contentNewTop,
- currentScale: content.currentScale,
- newScale: contentNewScale,
- };
- content.currentWidth = contentNewWidth;
- content.currentHeight = contentNewHeight;
- content.currentLeft = contentNewLeft;
- content.currentTop = contentNewTop;
- content.currentScale = contentNewScale;
- return response;
- },
- _transform: function _transform(_ref2) {
- _ref2.currentLeft;
- var newLeft = _ref2.newLeft;
- _ref2.currentTop;
- var newTop = _ref2.newTop;
- _ref2.currentScale;
- var newScale = _ref2.newScale;
- this.content.$element.style.transform = 'translate3d('
- .concat(newLeft, 'px, ')
- .concat(newTop, 'px, 0px) scale(')
- .concat(newScale, ')');
-
- if (typeof this.options.rescale === 'function') {
- this.options.rescale();
- }
- },
- _zoom: function _zoom(direction) {
- var windowPosition = getElementPosition(this.window.$element);
- var window = this.window;
- var _document2 = document,
- body = _document2.body,
- documentElement = _document2.documentElement;
- var scrollLeft =
- window.pageXOffset ||
- documentElement.scrollLeft ||
- body.scrollLeft;
- var scrollTop =
- window.pageYOffset ||
- documentElement.scrollTop ||
- body.scrollTop;
-
- this._transform(
- this._computeNewPosition(this._computeNewScale(direction), {
- x:
- windowPosition.left +
- this.window.originalWidth / 2 -
- scrollLeft,
- y:
- windowPosition.top +
- this.window.originalHeight / 2 -
- scrollTop,
- })
- );
- },
- prepare: function prepare() {
- this._prepare();
- },
- zoomUp: function zoomUp() {
- this._zoom(-1);
- },
- zoomDown: function zoomDown() {
- this._zoom(1);
- },
- destroy: function destroy() {
- this.content.$element.style.transform = '';
-
- if (this.options.type === 'image') {
- off(this.content.$element, 'load', this._init);
- }
-
- if (this.isTouch) {
- off(
- this.content.$element,
- 'touchmove',
- this._zoomTwoFingers_TouchmoveHandler
- );
- off(
- this.content.$element,
- 'touchend',
- this._zoomTwoFingers_TouchendHandler
- );
- }
-
- off(this.window.$element, 'wheel', this._wheelHandler);
-
- if (this.options.zoomOnClick) {
- off(
- this.window.$element,
- this.events.down,
- this._downHandler,
- this.events.options
- );
- off(
- this.window.$element,
- this.events.up,
- this._upHandler,
- this.events.options
- );
- }
-
- if (this.dragScrollable) {
- this.dragScrollable.destroy();
- }
-
- for (var key in this) {
- if (this.hasOwnProperty(key)) {
- this[key] = null;
- }
- }
- },
- };
-
- function _wheelHandler(event) {
- event.preventDefault();
-
- this._transform(
- this._computeNewPosition(this._computeNewScale(event.deltaY), {
- x: eventClientX(event),
- y: eventClientY(event),
- })
- );
- }
-
- function _downHandler(event) {
- var _this = this;
-
- if (
- (this.isTouch && event.touches.length === 1) ||
- event.buttons === 1
- ) {
- this.clickExpired = false;
- setTimeout(function () {
- return (_this.clickExpired = true);
- }, 150);
- }
- }
-
- function _upHandler(event) {
- if (!this.clickExpired) {
- this._transform(
- this._computeNewPosition(
- this.direction === 1
- ? this.content.maxScale
- : this.content.minScale,
- {
- x: eventClientX(event),
- y: eventClientY(event),
- }
- )
- );
-
- this.direction *= -1;
- }
- }
-
- function _zoomTwoFingers_TouchmoveHandler(event) {
- // detect two fingers
- if (event.targetTouches.length === 2) {
- var pageX1 = event.targetTouches[0].clientX;
- var pageY1 = event.targetTouches[0].clientY;
- var pageX2 = event.targetTouches[1].clientX;
- var pageY2 = event.targetTouches[1].clientY; // Math.hypot() analog
-
- var fingersHypotNew = Math.round(
- Math.sqrt(
- Math.pow(Math.abs(pageX1 - pageX2), 2) +
- Math.pow(Math.abs(pageY1 - pageY2), 2)
- )
- );
- var direction = 0;
- if (fingersHypotNew > this.fingersHypot + 5) direction = -1;
- if (fingersHypotNew < this.fingersHypot - 5) direction = 1;
-
- if (direction !== 0) {
- console.log(
- 'move',
- direction,
- this.fingersHypot,
- fingersHypotNew
- );
-
- if (this.fingersHypot !== null || direction === 1) {
- var eventEmulator = new Event('wheel'); // sized direction
-
- eventEmulator.deltaY = direction; // middle position between fingers
-
- eventEmulator.clientX =
- Math.min(pageX1, pageX2) +
- Math.abs(pageX1 - pageX2) / 2;
- eventEmulator.clientY =
- Math.min(pageY1, pageY2) +
- Math.abs(pageY1 - pageY2) / 2;
-
- this._wheelHandler(eventEmulator);
- }
-
- this.fingersHypot = fingersHypotNew;
- this.zoomPinchWasDetected = true;
- }
- }
- }
-
- function _zoomTwoFingers_TouchendHandler() {
- if (this.zoomPinchWasDetected) {
- this.fingersHypot = null;
- this.zoomPinchWasDetected = false;
- console.log('end', this.fingersHypot);
- }
- }
- /**
- * Create WZoom instance
- * @param {string|HTMLElement} selectorOrHTMLElement
- * @param {Object} [options]
- * @returns {WZoom}
- */
-
- WZoom.create = function (selectorOrHTMLElement, options) {
- return new WZoom(selectorOrHTMLElement, options);
- };
-
- return WZoom;
- });