vanilla-js-wheel-zoom
Version:
Image resizing using mouse wheel (pinch to zoom) + drag scrollable image (as well as any HTML content)
1,402 lines (1,355 loc) • 59.1 kB
JavaScript
(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';
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _assertThisInitialized(e) {
if (void 0 === e)
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
return e;
}
function _callSuper(t, o, e) {
return (
(o = _getPrototypeOf(o)),
_possibleConstructorReturn(
t,
_isNativeReflectConstruct()
? Reflect.construct(o, [], _getPrototypeOf(t).constructor)
: o.apply(t, e)
)
);
}
function _classCallCheck(a, n) {
if (!(a instanceof n))
throw new TypeError('Cannot call a class as a function');
}
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
(o.enumerable = o.enumerable || !1),
(o.configurable = !0),
'value' in o && (o.writable = !0),
Object.defineProperty(e, _toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return (
r && _defineProperties(e.prototype, r),
Object.defineProperty(e, 'prototype', {
writable: !1,
}),
e
);
}
function _createForOfIteratorHelper(r, e) {
var t =
('undefined' != typeof Symbol && r[Symbol.iterator]) ||
r['@@iterator'];
if (!t) {
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
t && (r = t);
var n = 0,
F = function () {};
return {
s: F,
n: function () {
return n >= r.length
? {
done: !0,
}
: {
done: !1,
value: r[n++],
};
},
e: function (r) {
throw r;
},
f: F,
};
}
throw new TypeError(
'Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'
);
}
var o,
a = !0,
u = !1;
return {
s: function () {
t = t.call(r);
},
n: function () {
var r = t.next();
return (a = r.done), r;
},
e: function (r) {
(u = !0), (o = r);
},
f: function () {
try {
a || null == t.return || t.return();
} finally {
if (u) throw o;
}
},
};
}
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _get() {
return (
(_get =
'undefined' != typeof Reflect && Reflect.get
? Reflect.get.bind()
: function (e, t, r) {
var p = _superPropBase(e, t);
if (p) {
var n = Object.getOwnPropertyDescriptor(p, t);
return n.get
? n.get.call(arguments.length < 3 ? e : r)
: n.value;
}
}),
_get.apply(null, arguments)
);
}
function _getPrototypeOf(t) {
return (
(_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf.bind()
: function (t) {
return t.__proto__ || Object.getPrototypeOf(t);
}),
_getPrototypeOf(t)
);
}
function _inherits(t, e) {
if ('function' != typeof e && null !== e)
throw new TypeError(
'Super expression must either be null or a function'
);
(t.prototype = Object.create(e && e.prototype, {
constructor: {
value: t,
writable: !0,
configurable: !0,
},
})),
Object.defineProperty(t, 'prototype', {
writable: !1,
}),
e && _setPrototypeOf(t, e);
}
function _isNativeReflectConstruct() {
try {
var t = !Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
} catch (t) {}
return (_isNativeReflectConstruct = function () {
return !!t;
})();
}
function _iterableToArrayLimit(r, l) {
var t =
null == r
? null
: ('undefined' != typeof Symbol && r[Symbol.iterator]) ||
r['@@iterator'];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (((i = (t = t.call(r)).next), 0 === l));
else
for (
;
!(f = (e = i.call(t)).done) &&
(a.push(e.value), a.length !== l);
f = !0
);
} catch (r) {
(o = !0), (n = r);
} finally {
try {
if (
!f &&
null != t.return &&
((u = t.return()), Object(u) !== u)
)
return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'
);
}
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r &&
(o = o.filter(function (r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})),
t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2
? ownKeys(Object(t), !0).forEach(function (r) {
_defineProperty(e, r, t[r]);
})
: Object.getOwnPropertyDescriptors
? Object.defineProperties(
e,
Object.getOwnPropertyDescriptors(t)
)
: ownKeys(Object(t)).forEach(function (r) {
Object.defineProperty(
e,
r,
Object.getOwnPropertyDescriptor(t, r)
);
});
}
return e;
}
function _possibleConstructorReturn(t, e) {
if (e && ('object' == typeof e || 'function' == typeof e)) return e;
if (void 0 !== e)
throw new TypeError(
'Derived constructors may only return object or undefined'
);
return _assertThisInitialized(t);
}
function _setPrototypeOf(t, e) {
return (
(_setPrototypeOf = Object.setPrototypeOf
? Object.setPrototypeOf.bind()
: function (t, e) {
return (t.__proto__ = e), t;
}),
_setPrototypeOf(t, e)
);
}
function _slicedToArray(r, e) {
return (
_arrayWithHoles(r) ||
_iterableToArrayLimit(r, e) ||
_unsupportedIterableToArray(r, e) ||
_nonIterableRest()
);
}
function _superPropBase(t, o) {
for (
;
!{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t));
);
return t;
}
function _superPropGet(t, e, o, r) {
var p = _get(_getPrototypeOf(t.prototype), e, o);
return 'function' == typeof p
? function (t) {
return p.apply(o, t);
}
: p;
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r);
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return String(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ('string' == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return (
'Object' === t && r.constructor && (t = r.constructor.name),
'Map' === t || 'Set' === t
? Array.from(r)
: 'Arguments' === t ||
/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)
? _arrayLikeToArray(r, a)
: void 0
);
}
}
/**
* 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 = getPageScrollTop();
var scrollLeft = getPageScrollLeft();
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,
};
}
/**
* Get page scroll left
* @returns {number}
*/
function getPageScrollLeft() {
var supportPageOffset = window.pageXOffset !== undefined;
var isCSS1Compat = (document.compatMode || '') === 'CSS1Compat';
return supportPageOffset
? window.pageXOffset
: isCSS1Compat
? document.documentElement.scrollLeft
: document.body.scrollLeft;
}
/**
* Get page scroll top
* @returns {number}
*/
function getPageScrollTop() {
var supportPageOffset = window.pageYOffset !== undefined;
var isCSS1Compat = (document.compatMode || '') === 'CSS1Compat';
return supportPageOffset
? window.pageYOffset
: isCSS1Compat
? document.documentElement.scrollTop
: document.body.scrollTop;
}
/**
* @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);
}
/**
* @returns {boolean}
*/
function isTouch() {
return (
'ontouchstart' in window ||
navigator.MaxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
);
}
/**
* @param {Event} event
* @returns {number}
*/
function eventClientX(event) {
return event.type === 'wheel' ||
event.type === 'pointerup' ||
event.type === 'pointerdown' ||
event.type === 'pointermove' ||
event.type === 'mousedown' ||
event.type === 'mousemove' ||
event.type === 'mouseup'
? event.clientX
: event.changedTouches[0].clientX;
}
/**
* @param {Event} event
* @returns {number}
*/
function eventClientY(event) {
return event.type === 'wheel' ||
event.type === 'pointerup' ||
event.type === 'pointerdown' ||
event.type === 'pointermove' ||
event.type === 'mousedown' ||
event.type === 'mousemove' ||
event.type === 'mouseup'
? event.clientY
: event.changedTouches[0].clientY;
}
/**
* @param {HTMLElement} $element
* @param {number} left
* @param {number} top
* @param {number} scale
*/
function transform($element, left, top, scale) {
$element.style.transform = 'translate('
.concat(left, 'px, ')
.concat(top, 'px) scale(')
.concat(scale, ')');
}
/**
* @param {HTMLElement} $element
* @param {number} time
*/
function transition($element, time) {
if (time) {
$element.style.transition = 'transform '.concat(time, 's');
} else {
$element.style.removeProperty('transition');
}
}
/**
* @param {WZoomViewport} viewport
* @param {WZoomContent} content
* @param {string} align
* @returns {number[]}
*/
function calculateAlignPoint(viewport, content, align) {
var pointX = 0;
var pointY = 0;
switch (align) {
case 'top':
pointY = (content.currentHeight - viewport.originalHeight) / 2;
break;
case 'right':
pointX =
((content.currentWidth - viewport.originalWidth) / 2) * -1;
break;
case 'bottom':
pointY =
((content.currentHeight - viewport.originalHeight) / 2) *
-1;
break;
case 'left':
pointX = (content.currentWidth - viewport.originalWidth) / 2;
break;
}
return [pointX, pointY];
}
/**
* @param {WZoomViewport} viewport
* @param {WZoomContent} content
* @param {string} align
* @returns {number[]}
*/
function calculateCorrectPoint(viewport, content, align) {
var pointX = Math.max(
0,
(viewport.originalWidth - content.currentWidth) / 2
);
var pointY = Math.max(
0,
(viewport.originalHeight - content.currentHeight) / 2
);
switch (align) {
case 'top':
pointY = 0;
break;
case 'right':
pointX = 0;
break;
case 'bottom':
pointY = pointY * 2;
break;
case 'left':
pointX = pointX * 2;
break;
}
return [pointX, pointY];
}
/**
* @returns {number}
*/
function calculateContentShift(
axisValue,
axisScroll,
axisViewportPosition,
axisContentPosition,
originalViewportSize,
contentSizeRatio
) {
var viewportShift = axisValue + axisScroll - axisViewportPosition;
var centerViewportShift = originalViewportSize / 2 - viewportShift;
var centerContentShift = centerViewportShift + axisContentPosition;
return (
centerContentShift * contentSizeRatio -
centerContentShift +
axisContentPosition
);
}
function calculateContentMaxShift(
align,
originalViewportSize,
correctCoordinate,
size,
shift
) {
switch (align) {
case 'left':
if (size / 2 - shift < originalViewportSize / 2) {
shift = (size - originalViewportSize) / 2;
}
break;
case 'right':
if (size / 2 + shift < originalViewportSize / 2) {
shift = ((size - originalViewportSize) / 2) * -1;
}
break;
default:
if (
(size - originalViewportSize) / 2 + correctCoordinate <
Math.abs(shift)
) {
var positive = shift < 0 ? -1 : 1;
shift =
((size - originalViewportSize) / 2 +
correctCoordinate) *
positive;
}
}
return shift;
}
/**
* @param {WZoomViewport} viewport
* @returns {{x: number, y: number}}
*/
function calculateViewportCenter(viewport) {
var viewportPosition = getElementPosition(viewport.$element);
return {
x:
viewportPosition.left +
viewport.originalWidth / 2 -
getPageScrollLeft(),
y:
viewportPosition.top +
viewport.originalHeight / 2 -
getPageScrollTop(),
};
}
/** @type {WZoomOptions} */
var wZoomDefaultOptions = {
// 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,
// minimum allowed proportion of scale (computed auto if null)
minScale: null,
// maximum allowed proportion of scale (1 = 100% content size)
maxScale: 1,
// content resizing speed
speed: 1.1,
// zoom to maximum (minimum) size on click
zoomOnClick: true,
// zoom to maximum (minimum) size on double click
zoomOnDblClick: false,
// smooth extinction
smoothTime: 0.25,
// align content `center`, `left`, `top`, `right`, `bottom`
alignContent: 'center',
// ******************** //
disableWheelZoom: false,
// option to reverse wheel direction
reverseWheelDirection: false,
// ******************** //
// drag scrollable content
dragScrollable: true,
};
/**
* @typedef WZoomOptions
* @type {Object}
* @property {string} type
* @property {?number} width
* @property {?number} height
* @property {?number} minScale
* @property {number} maxScale
* @property {number} speed
* @property {boolean} zoomOnClick
* @property {boolean} zoomOnDblClick
* @property {number} smoothTime
* @property {string} alignContent
* @property {boolean} disableWheelZoom
* @property {boolean} reverseWheelDirection
* @property {boolean} dragScrollable
* @property {number} smoothTimeDrag
* @property {?Function} onGrab
* @property {?Function} onMove
* @property {?Function} onDrop
*/
var AbstractObserver = /*#__PURE__*/ (function () {
/**
* @constructor
*/
function AbstractObserver() {
_classCallCheck(this, AbstractObserver);
/** @type {Object<string, (event: Event) => void>} */
this.subscribes = {};
}
/**
* @param {string} eventType
* @param {(event: Event) => void} eventHandler
* @returns {AbstractObserver}
*/
return _createClass(AbstractObserver, [
{
key: 'on',
value: function on(eventType, eventHandler) {
if (!(eventType in this.subscribes)) {
this.subscribes[eventType] = [];
}
this.subscribes[eventType].push(eventHandler);
return this;
},
},
{
key: 'destroy',
value: function destroy() {
for (var key in this) {
if (this.hasOwnProperty(key)) {
this[key] = null;
}
}
},
/**
* @param {string} eventType
* @param {Event} event
* @protected
*/
},
{
key: '_run',
value: function _run(eventType, event) {
if (this.subscribes[eventType]) {
var _iterator = _createForOfIteratorHelper(
this.subscribes[eventType]
),
_step;
try {
for (
_iterator.s();
!(_step = _iterator.n()).done;
) {
var eventHandler = _step.value;
eventHandler(event);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
},
},
]);
})();
var EVENT_GRAB = 'grab';
var EVENT_MOVE = 'move';
var EVENT_DROP = 'drop';
var DragScrollableObserver = /*#__PURE__*/ (function (_AbstractObserver) {
/**
* @param {HTMLElement} target
* @constructor
*/
function DragScrollableObserver(target) {
var _this;
_classCallCheck(this, DragScrollableObserver);
_this = _callSuper(this, DragScrollableObserver);
_this.target = target;
_this.moveTimer = null;
_this.coordinates = null;
_this.coordinatesShift = null;
// 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._dropHandler = _this._dropHandler.bind(_this);
_this._grabHandler = _this._grabHandler.bind(_this);
_this._moveHandler = _this._moveHandler.bind(_this);
on(
_this.target,
_this.events.grab,
_this._grabHandler,
_this.events.options
);
return _this;
}
_inherits(DragScrollableObserver, _AbstractObserver);
return _createClass(DragScrollableObserver, [
{
key: 'destroy',
value: function destroy() {
off(
this.target,
this.events.grab,
this._grabHandler,
this.events.options
);
off(document, this.events.drop, this._dropHandler);
off(document, this.events.move, this._moveHandler);
_superPropGet(DragScrollableObserver, 'destroy', this)([]);
},
/**
* @param {Event|TouchEvent|MouseEvent} event
* @private
*/
},
{
key: '_grabHandler',
value: function _grabHandler(event) {
// if touch started (only one finger) or pressed left mouse button
if (
(this.isTouch && event.touches.length === 1) ||
event.buttons === 1
) {
this.coordinates = {
x: eventClientX(event),
y: eventClientY(event),
};
this.coordinatesShift = {
x: 0,
y: 0,
};
on(
document,
this.events.drop,
this._dropHandler,
this.events.options
);
on(
document,
this.events.move,
this._moveHandler,
this.events.options
);
this._run(EVENT_GRAB, event);
}
},
/**
* @param {Event} event
* @private
*/
},
{
key: '_dropHandler',
value: function _dropHandler(event) {
off(document, this.events.drop, this._dropHandler);
off(document, this.events.move, this._moveHandler);
this._run(EVENT_DROP, event);
},
/**
* @param {Event|TouchEvent} event
* @private
*/
},
{
key: '_moveHandler',
value: function _moveHandler(event) {
// so that it does not move when the touch screen and more than one finger
if (this.isTouch && event.touches.length > 1) return false;
var coordinatesShift = this.coordinatesShift,
coordinates = this.coordinates;
// change of the coordinate of the mouse cursor along the X/Y axis
coordinatesShift.x = eventClientX(event) - coordinates.x;
coordinatesShift.y = eventClientY(event) - coordinates.y;
coordinates.x = eventClientX(event);
coordinates.y = eventClientY(event);
clearTimeout(this.moveTimer);
// reset shift if cursor stops
this.moveTimer = setTimeout(function () {
coordinatesShift.x = 0;
coordinatesShift.y = 0;
}, 50);
event.data = _objectSpread2(
_objectSpread2({}, event.data || {}),
{},
{
x: coordinatesShift.x,
y: coordinatesShift.y,
}
);
this._run(EVENT_MOVE, event);
},
},
]);
})(AbstractObserver);
var EVENT_CLICK = 'click';
var EVENT_DBLCLICK = 'dblclick';
var EVENT_WHEEL = 'wheel';
var InteractionObserver = /*#__PURE__*/ (function (_AbstractObserver) {
/**
* @param {HTMLElement} target
* @constructor
*/
function InteractionObserver(target) {
var _this;
_classCallCheck(this, InteractionObserver);
_this = _callSuper(this, InteractionObserver);
_this.target = target;
_this.coordsOnDown = null;
_this.pressingTimeout = null;
_this.firstClick = true;
// 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;
_this._downHandler = _this._downHandler.bind(_this);
_this._upHandler = _this._upHandler.bind(_this);
_this._wheelHandler = _this._wheelHandler.bind(_this);
on(
_this.target,
_this.events.down,
_this._downHandler,
_this.events.options
);
on(
_this.target,
_this.events.up,
_this._upHandler,
_this.events.options
);
on(_this.target, EVENT_WHEEL, _this._wheelHandler);
return _this;
}
_inherits(InteractionObserver, _AbstractObserver);
return _createClass(InteractionObserver, [
{
key: 'destroy',
value: function destroy() {
off(
this.target,
this.events.down,
this._downHandler,
this.events.options
);
off(
this.target,
this.events.up,
this._upHandler,
this.events.options
);
off(
this.target,
EVENT_WHEEL,
this._wheelHandler,
this.events.options
);
_superPropGet(InteractionObserver, 'destroy', this)([]);
},
/**
* @param {TouchEvent|MouseEvent|PointerEvent} event
* @private
*/
},
{
key: '_downHandler',
value: function _downHandler(event) {
this.coordsOnDown = null;
if (
(this.isTouch && event.touches.length === 1) ||
event.buttons === 1
) {
this.coordsOnDown = {
x: eventClientX(event),
y: eventClientY(event),
};
}
clearTimeout(this.pressingTimeout);
},
/**
* @param {TouchEvent|MouseEvent|PointerEvent} event
* @private
*/
},
{
key: '_upHandler',
value: function _upHandler(event) {
var _this2 = this;
var delay = 200;
var setTimeoutInner = this.subscribes[EVENT_DBLCLICK]
? setTimeout
: function (cb, delay) {
return cb();
};
if (this.firstClick) {
this.firstClick = false;
this.pressingTimeout = setTimeoutInner(function () {
if (!_this2._isDetectedShift(event)) {
_this2._run(EVENT_CLICK, event);
}
_this2.firstClick = true;
}, delay);
} else {
this.pressingTimeout = setTimeoutInner(function () {
if (!_this2._isDetectedShift(event)) {
_this2._run(EVENT_DBLCLICK, event);
}
_this2.firstClick = true;
}, delay / 2);
}
},
/**
* @param {WheelEvent} event
* @private
*/
},
{
key: '_wheelHandler',
value: function _wheelHandler(event) {
this._run(EVENT_WHEEL, event);
},
/**
* @param {TouchEvent|MouseEvent|PointerEvent} event
* @return {boolean}
* @private
*/
},
{
key: '_isDetectedShift',
value: function _isDetectedShift(event) {
return !(
this.coordsOnDown &&
this.coordsOnDown.x === eventClientX(event) &&
this.coordsOnDown.y === eventClientY(event)
);
},
},
]);
})(AbstractObserver);
var EVENT_PINCH_TO_ZOOM = 'pinchtozoom';
var SHIFT_DECIDE_THAT_MOVE_STARTED = 5;
var PinchToZoomObserver = /*#__PURE__*/ (function (_AbstractObserver) {
/**
* @param {HTMLElement} target
* @constructor
*/
function PinchToZoomObserver(target) {
var _this;
_classCallCheck(this, PinchToZoomObserver);
_this = _callSuper(this, PinchToZoomObserver);
_this.target = target;
_this.fingersHypot = null;
_this.zoomPinchWasDetected = false;
_this._touchMoveHandler = _this._touchMoveHandler.bind(_this);
_this._touchEndHandler = _this._touchEndHandler.bind(_this);
on(_this.target, 'touchmove', _this._touchMoveHandler);
on(_this.target, 'touchend', _this._touchEndHandler);
return _this;
}
_inherits(PinchToZoomObserver, _AbstractObserver);
return _createClass(PinchToZoomObserver, [
{
key: 'destroy',
value: function destroy() {
off(this.target, 'touchmove', this._touchMoveHandler);
off(this.target, 'touchend', this._touchEndHandler);
_superPropGet(PinchToZoomObserver, 'destroy', this)([]);
},
/**
* @param {TouchEvent|PointerEvent} event
* @private
*/
},
{
key: '_touchMoveHandler',
value: function _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 + SHIFT_DECIDE_THAT_MOVE_STARTED
)
direction = -1;
if (
fingersHypotNew <
this.fingersHypot - SHIFT_DECIDE_THAT_MOVE_STARTED
)
direction = 1;
if (direction !== 0) {
if (this.fingersHypot !== null || direction === 1) {
// middle position between fingers
var clientX =
Math.min(pageX1, pageX2) +
Math.abs(pageX1 - pageX2) / 2;
var clientY =
Math.min(pageY1, pageY2) +
Math.abs(pageY1 - pageY2) / 2;
event.data = _objectSpread2(
_objectSpread2({}, event.data || {}),
{},
{
clientX: clientX,
clientY: clientY,
direction: direction,
}
);
this._run(EVENT_PINCH_TO_ZOOM, event);
}
this.fingersHypot = fingersHypotNew;
this.zoomPinchWasDetected = true;
}
}
},
/**
* @private
*/
},
{
key: '_touchEndHandler',
value: function _touchEndHandler() {
if (this.zoomPinchWasDetected) {
this.fingersHypot = null;
this.zoomPinchWasDetected = false;
}
},
},
]);
})(AbstractObserver);
/**
* @class WZoom
* @param {string|HTMLElement} selectorOrHTMLElement
* @param {WZoomOptions} 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._computeScale = this._computeScale.bind(this);
this._computePosition = this._computePosition.bind(this);
this._transform = this._transform.bind(this);
/** @type {WZoomContent} */
this.content = {};
if (typeof selectorOrHTMLElement === 'string') {
this.content.$element = document.querySelector(
selectorOrHTMLElement
);
if (!this.content.$element) {
throw 'WZoom: Element with selector `'.concat(
selectorOrHTMLElement,
'` not found'
);
}
} else if (selectorOrHTMLElement instanceof HTMLElement) {
this.content.$element = selectorOrHTMLElement;
} else {
throw 'WZoom: `selectorOrHTMLElement` must be selector or HTMLElement, and not '.concat(
{}.toString.call(selectorOrHTMLElement)
);
}
/** @type {WZoomViewport} */
this.viewport = {};
// for viewport take just the parent
this.viewport.$element = this.content.$element.parentElement;
/** @type {WZoomOptions} */
this.options = optionsConstructor(options, wZoomDefaultOptions);
// check if we're using a touch screen
this.isTouch = isTouch();
this.direction = 1;
/** @type {AbstractObserver[]} */
this.observers = [];
if (this.options.type === 'image') {
// if the `image` has already been loaded
if (this.content.$element.complete) {
this._init();
} else {
on(this.content.$element, 'load', this._init, {
once: true,
});
}
} else {
this._init();
}
}
WZoom.prototype = {
constructor: WZoom,
/**
* @private
*/
_init: function _init() {
var _this = this;
var viewport = this.viewport,
content = this.content,
options = this.options,
observers = this.observers;
this._prepare();
this._destroyObservers();
if (options.dragScrollable === true) {
var dragScrollableObserver = new DragScrollableObserver(
content.$element
);
observers.push(dragScrollableObserver);
if (typeof options.onGrab === 'function') {
dragScrollableObserver.on(EVENT_GRAB, function (event) {
event.preventDefault();
options.onGrab(event, _this);
});
}
if (typeof options.onDrop === 'function') {
dragScrollableObserver.on(EVENT_DROP, function (event) {
event.preventDefault();
options.onDrop(event, _this);
});
}
dragScrollableObserver.on(EVENT_MOVE, function (event) {
event.preventDefault();
var _event$data = event.data,
x = _event$data.x,
y = _event$data.y;
var contentNewLeft = content.currentLeft + x;
var contentNewTop = content.currentTop + y;
var maxAvailableLeft =
(content.currentWidth - viewport.originalWidth) / 2 +
content.correctX;
var maxAvailableTop =
(content.currentHeight - viewport.originalHeight) / 2 +
content.correctY;
// if we do not go beyond the permissible boundaries of the viewport
if (Math.abs(contentNewLeft) <= maxAvailableLeft)
content.currentLeft = contentNewLeft;
// if we do not go beyond the permissible boundaries of the viewport
if (Math.abs(contentNewTop) <= maxAvailableTop)
content.currentTop = contentNewTop;
_this._transform(options.smoothTimeDrag);
if (typeof options.onMove === 'function') {
options.onMove(event, _this);
}
});
}
var interactionObserver = new InteractionObserver(content.$element);
observers.push(interactionObserver);
if (!options.disableWheelZoom) {
if (this.isTouch) {
var pinchToZoomObserver = new PinchToZoomObserver(
content.$element
);
observers.push(pinchToZoomObserver);
pinchToZoomObserver.on(
EVENT_PINCH_TO_ZOOM,
function (event) {
var _event$data2 = event.data,
clientX = _event$data2.clientX,
clientY = _event$data2.clientY,
direction = _event$data2.direction;
var scale = _this._computeScale(direction);
_this._computePosition(scale, clientX, clientY);
_this._transform();
}
);
} else {
interactionObserver.on(EVENT_WHEEL, function (event) {
event.preventDefault();
var direction = options.reverseWheelDirection
? -event.deltaY
: event.deltaY;
var scale = _this._computeScale(direction);
_this._computePosition(
scale,
eventClientX(event),
eventClientY(event)
);
_this._transform();
});
}
}
if (options.zoomOnClick || options.zoomOnDblClick) {
var eventType = options.zoomOnDblClick
? EVENT_DBLCLICK
: EVENT_CLICK;
interactionObserver.on(eventType, function (event) {
var scale =
_this.direction === 1
? content.maxScale
: content.minScale;
_this._computePosition(
scale,
eventClientX(event),
eventClientY(event)
);
_this._transform();
_this.direction *= -1;
});
}
},
/**
* @private
*/
_prepare: function _prepare() {
var viewport = this.viewport,
content = this.content,
options = this.options;
var _getElementPosition = getElementPosition(viewport.$element),
left = _getElementPosition.left,
top = _getElementPosition.top;
viewport.originalWidth = viewport.$element.offsetWidth;
viewport.originalHeight = viewport.$element.offsetHeight;
viewport.originalLeft = left;
viewport.originalTop = top;
if (options.type === 'image') {
content.originalWidth =
options.width || content.$element.naturalWidth;
content.originalHeight =
options.height || content.$element.naturalHeight;
} else {
content.originalWidth =
options.width || content.$element.offsetWidth;
content.originalHeight =
options.height || content.$element.offsetHeight;
}
content.maxScale = options.maxScale;
content.minScale =
options.minScale ||
Math.min(
viewport.originalWidth / content.originalWidth,
viewport.originalHeight / content.originalHeight,
content.maxScale
);
content.currentScale = content.minScale;
content.currentWidth = content.originalWidth * content.currentScale;
content.currentHeight =
content.originalHeight * content.currentScale;
var _calculateAlignPoint = calculateAlignPoint(
viewport,
content,
options.alignContent
);
var _calculateAlignPoint2 = _slicedToArray(_calculateAlignPoint, 2);
content.alignPointX = _calculateAlignPoint2[0];
content.alignPointY = _calculateAlignPoint2[1];
content.currentLeft = content.alignPointX;
content.currentTop = content.alignPointY;
// calculate indent-left and indent-top to of content from viewport borders
var _calculateCorrectPoin = calculateCorrectPoint(
viewport,
content,
options.alignContent
);
var _calculateCorrectPoin2 = _slicedToArray(
_calculateCorrectPoin,
2
);
content.correctX = _calculateCorrectPoin2[0];
content.correctY = _calculateCorrectPoin2[1];
if (typeof options.prepare === 'function') {
options.prepare(this);
}
this._transform();
},
/**
* @private
*/
_computeScale: function _computeScale(direction) {
this.direction = direction < 0 ? 1 : -1;
var _this$content = this.content,
minScale = _this$content.minScale,