| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927 |
- /*
- PinchZoom.js
- Copyright (c) Manuel Stofer 2013 - today
- Author: Manuel Stofer (mst@rtp.ch)
- Version: 2.3.5
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- // polyfills
- if (typeof Object.assign != 'function') {
- // Must be writable: true, enumerable: false, configurable: true
- Object.defineProperty(Object, "assign", {
- value: function assign(target, varArgs) { // .length of function is 2
- if (target == null) { // TypeError if undefined or null
- throw new TypeError('Cannot convert undefined or null to object');
- }
- var to = Object(target);
- for (var index = 1; index < arguments.length; index++) {
- var nextSource = arguments[index];
- if (nextSource != null) { // Skip over if undefined or null
- for (var nextKey in nextSource) {
- // Avoid bugs when hasOwnProperty is shadowed
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
- to[nextKey] = nextSource[nextKey];
- }
- }
- }
- }
- return to;
- },
- writable: true,
- configurable: true
- });
- }
- if (typeof Array.from != 'function') {
- Array.from = function (object) {
- return [].slice.call(object);
- };
- }
- // utils
- var buildElement = function(str) {
- // empty string as title argument required by IE and Edge
- var tmp = document.implementation.createHTMLDocument('');
- tmp.body.innerHTML = str;
- return Array.from(tmp.body.children)[0];
- };
- var triggerEvent = function(el, name) {
- var event = document.createEvent('HTMLEvents');
- event.initEvent(name, true, false);
- el.dispatchEvent(event);
- };
- var definePinchZoom = function () {
- /**
- * Pinch zoom
- * @param el
- * @param options
- * @constructor
- */
- var PinchZoom = function (el, options) {
- this.el = el;
- this.zoomFactor = 1;
- this.lastScale = 1;
- this.offset = {
- x: 0,
- y: 0
- };
- this.initialOffset = {
- x: 0,
- y: 0,
- };
- this.options = Object.assign({}, this.defaults, options);
- this.setupMarkup();
- this.bindEvents();
- this.update();
- // The image may already be loaded when PinchZoom is initialized,
- // and then the load event (which trigger update) will never fire.
- if (this.isImageLoaded(this.el)) {
- this.updateAspectRatio();
- this.setupOffsets();
- }
- this.enable();
- },
- sum = function (a, b) {
- return a + b;
- },
- isCloseTo = function (value, expected) {
- return value > expected - 0.01 && value < expected + 0.01;
- };
- PinchZoom.prototype = {
- defaults: {
- tapZoomFactor: 2,
- zoomOutFactor: 1.3,
- animationDuration: 300,
- maxZoom: 4,
- minZoom: 0.5,
- draggableUnzoomed: true,
- lockDragAxis: false,
- setOffsetsOnce: false,
- use2d: true,
- zoomStartEventName: 'pz_zoomstart',
- zoomUpdateEventName: 'pz_zoomupdate',
- zoomEndEventName: 'pz_zoomend',
- dragStartEventName: 'pz_dragstart',
- dragUpdateEventName: 'pz_dragupdate',
- dragEndEventName: 'pz_dragend',
- doubleTapEventName: 'pz_doubletap',
- verticalPadding: 0,
- horizontalPadding: 0,
- onZoomStart: null,
- onZoomEnd: null,
- onZoomUpdate: null,
- onDragStart: null,
- onDragEnd: null,
- onDragUpdate: null,
- onDoubleTap: null
- },
- /**
- * Event handler for 'dragstart'
- * @param event
- */
- handleDragStart: function (event) {
- triggerEvent(this.el, this.options.dragStartEventName);
- if(typeof this.options.onDragStart == "function"){
- this.options.onDragStart(this, event)
- }
- this.stopAnimation();
- this.lastDragPosition = false;
- this.hasInteraction = true;
- this.handleDrag(event);
- },
- /**
- * Event handler for 'drag'
- * @param event
- */
- handleDrag: function (event) {
- var touch = this.getTouches(event)[0];
- this.drag(touch, this.lastDragPosition);
- this.offset = this.sanitizeOffset(this.offset);
- this.lastDragPosition = touch;
- },
- handleDragEnd: function () {
- triggerEvent(this.el, this.options.dragEndEventName);
- if(typeof this.options.onDragEnd == "function"){
- this.options.onDragEnd(this, event)
- }
- this.end();
- },
- /**
- * Event handler for 'zoomstart'
- * @param event
- */
- handleZoomStart: function (event) {
- triggerEvent(this.el, this.options.zoomStartEventName);
- if(typeof this.options.onZoomStart == "function"){
- this.options.onZoomStart(this, event)
- }
- this.stopAnimation();
- this.lastScale = 1;
- this.nthZoom = 0;
- this.lastZoomCenter = false;
- this.hasInteraction = true;
- },
- /**
- * Event handler for 'zoom'
- * @param event
- */
- handleZoom: function (event, newScale) {
- // a relative scale factor is used
- var touchCenter = this.getTouchCenter(this.getTouches(event)),
- scale = newScale / this.lastScale;
- this.lastScale = newScale;
- // the first touch events are thrown away since they are not precise
- this.nthZoom += 1;
- if (this.nthZoom > 3) {
- this.scale(scale, touchCenter);
- this.drag(touchCenter, this.lastZoomCenter);
- }
- this.lastZoomCenter = touchCenter;
- },
- handleZoomEnd: function () {
- triggerEvent(this.el, this.options.zoomEndEventName);
- if(typeof this.options.onZoomEnd == "function"){
- this.options.onZoomEnd(this, event)
- }
- this.end();
- },
- /**
- * Event handler for 'doubletap'
- * @param event
- */
- handleDoubleTap: function (event) {
- var center = this.getTouches(event)[0],
- zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor,
- startZoomFactor = this.zoomFactor,
- updateProgress = (function (progress) {
- this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
- }).bind(this);
- if (this.hasInteraction) {
- return;
- }
- this.isDoubleTap = true;
- if (startZoomFactor > zoomFactor) {
- center = this.getCurrentZoomCenter();
- }
- this.animate(this.options.animationDuration, updateProgress, this.swing);
- triggerEvent(this.el, this.options.doubleTapEventName);
- if(typeof this.options.onDoubleTap == "function"){
- this.options.onDoubleTap(this, event)
- }
- },
- /**
- * Compute the initial offset
- *
- * the element should be centered in the container upon initialization
- */
- computeInitialOffset: function () {
- this.initialOffset = {
- x: -Math.abs(this.el.offsetWidth * this.getInitialZoomFactor() - this.container.offsetWidth) / 2,
- y: -Math.abs(this.el.offsetHeight * this.getInitialZoomFactor() - this.container.offsetHeight) / 2,
- };
- },
- /**
- * Reset current image offset to that of the initial offset
- */
- resetOffset: function() {
- this.offset.x = this.initialOffset.x;
- this.offset.y = this.initialOffset.y;
- },
- /**
- * Determine if image is loaded
- */
- isImageLoaded: function (el) {
- if (el.nodeName === 'IMG') {
- return el.complete && el.naturalHeight !== 0;
- } else {
- return Array.from(el.querySelectorAll('img')).every(this.isImageLoaded);
- }
- },
- setupOffsets: function() {
- if (this.options.setOffsetsOnce && this._isOffsetsSet) {
- return;
- }
- this._isOffsetsSet = true;
- this.computeInitialOffset();
- this.resetOffset();
- },
- /**
- * Max / min values for the offset
- * @param offset
- * @return {Object} the sanitized offset
- */
- sanitizeOffset: function (offset) {
- var elWidth = this.el.offsetWidth * this.getInitialZoomFactor() * this.zoomFactor;
- var elHeight = this.el.offsetHeight * this.getInitialZoomFactor() * this.zoomFactor;
- var maxX = elWidth - this.getContainerX() + this.options.horizontalPadding,
- maxY = elHeight - this.getContainerY() + this.options.verticalPadding,
- maxOffsetX = Math.max(maxX, 0),
- maxOffsetY = Math.max(maxY, 0),
- minOffsetX = Math.min(maxX, 0) - this.options.horizontalPadding,
- minOffsetY = Math.min(maxY, 0) - this.options.verticalPadding;
- return {
- x: Math.min(Math.max(offset.x, minOffsetX), maxOffsetX),
- y: Math.min(Math.max(offset.y, minOffsetY), maxOffsetY)
- };
- },
- /**
- * Scale to a specific zoom factor (not relative)
- * @param zoomFactor
- * @param center
- */
- scaleTo: function (zoomFactor, center) {
- this.scale(zoomFactor / this.zoomFactor, center);
- },
- /**
- * Scales the element from specified center
- * @param scale
- * @param center
- */
- scale: function (scale, center) {
- scale = this.scaleZoomFactor(scale);
- this.addOffset({
- x: (scale - 1) * (center.x + this.offset.x),
- y: (scale - 1) * (center.y + this.offset.y)
- });
- triggerEvent(this.el, this.options.zoomUpdateEventName);
- if(typeof this.options.onZoomUpdate == "function"){
- this.options.onZoomUpdate(this, event)
- }
- },
- /**
- * Scales the zoom factor relative to current state
- * @param scale
- * @return the actual scale (can differ because of max min zoom factor)
- */
- scaleZoomFactor: function (scale) {
- var originalZoomFactor = this.zoomFactor;
- this.zoomFactor *= scale;
- this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom));
- return this.zoomFactor / originalZoomFactor;
- },
- /**
- * Determine if the image is in a draggable state
- *
- * When the image can be dragged, the drag event is acted upon and cancelled.
- * When not draggable, the drag event bubbles through this component.
- *
- * @return {Boolean}
- */
- canDrag: function () {
- return this.options.draggableUnzoomed || !isCloseTo(this.zoomFactor, 1);
- },
- /**
- * Drags the element
- * @param center
- * @param lastCenter
- */
- drag: function (center, lastCenter) {
- if (lastCenter) {
- if(this.options.lockDragAxis) {
- // lock scroll to position that was changed the most
- if(Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) {
- this.addOffset({
- x: -(center.x - lastCenter.x),
- y: 0
- });
- }
- else {
- this.addOffset({
- y: -(center.y - lastCenter.y),
- x: 0
- });
- }
- }
- else {
- this.addOffset({
- y: -(center.y - lastCenter.y),
- x: -(center.x - lastCenter.x)
- });
- }
- triggerEvent(this.el, this.options.dragUpdateEventName);
- if(typeof this.options.onDragUpdate == "function"){
- this.options.onDragUpdate(this, event)
- }
- }
- },
- /**
- * Calculates the touch center of multiple touches
- * @param touches
- * @return {Object}
- */
- getTouchCenter: function (touches) {
- return this.getVectorAvg(touches);
- },
- /**
- * Calculates the average of multiple vectors (x, y values)
- */
- getVectorAvg: function (vectors) {
- return {
- x: vectors.map(function (v) { return v.x; }).reduce(sum) / vectors.length,
- y: vectors.map(function (v) { return v.y; }).reduce(sum) / vectors.length
- };
- },
- /**
- * Adds an offset
- * @param offset the offset to add
- * @return return true when the offset change was accepted
- */
- addOffset: function (offset) {
- this.offset = {
- x: this.offset.x + offset.x,
- y: this.offset.y + offset.y
- };
- },
- sanitize: function () {
- if (this.zoomFactor < this.options.zoomOutFactor) {
- this.zoomOutAnimation();
- } else if (this.isInsaneOffset(this.offset)) {
- this.sanitizeOffsetAnimation();
- }
- },
- /**
- * Checks if the offset is ok with the current zoom factor
- * @param offset
- * @return {Boolean}
- */
- isInsaneOffset: function (offset) {
- var sanitizedOffset = this.sanitizeOffset(offset);
- return sanitizedOffset.x !== offset.x ||
- sanitizedOffset.y !== offset.y;
- },
- /**
- * Creates an animation moving to a sane offset
- */
- sanitizeOffsetAnimation: function () {
- var targetOffset = this.sanitizeOffset(this.offset),
- startOffset = {
- x: this.offset.x,
- y: this.offset.y
- },
- updateProgress = (function (progress) {
- this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x);
- this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y);
- this.update();
- }).bind(this);
- this.animate(
- this.options.animationDuration,
- updateProgress,
- this.swing
- );
- },
- /**
- * Zooms back to the original position,
- * (no offset and zoom factor 1)
- */
- zoomOutAnimation: function () {
- if (this.zoomFactor === 1) {
- return;
- }
- var startZoomFactor = this.zoomFactor,
- zoomFactor = 1,
- center = this.getCurrentZoomCenter(),
- updateProgress = (function (progress) {
- this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
- }).bind(this);
- this.animate(
- this.options.animationDuration,
- updateProgress,
- this.swing
- );
- },
- /**
- * Updates the container aspect ratio
- *
- * Any previous container height must be cleared before re-measuring the
- * parent height, since it depends implicitly on the height of any of its children
- */
- updateAspectRatio: function () {
- this.unsetContainerY();
- this.setContainerY(this.container.parentElement.offsetHeight);
- },
- /**
- * Calculates the initial zoom factor (for the element to fit into the container)
- * @return {number} the initial zoom factor
- */
- getInitialZoomFactor: function () {
- var xZoomFactor = this.container.offsetWidth / this.el.offsetWidth;
- var yZoomFactor = this.container.offsetHeight / this.el.offsetHeight;
- return Math.min(xZoomFactor, yZoomFactor);
- },
- /**
- * Calculates the aspect ratio of the element
- * @return the aspect ratio
- */
- getAspectRatio: function () {
- return this.el.offsetWidth / this.el.offsetHeight;
- },
- /**
- * Calculates the virtual zoom center for the current offset and zoom factor
- * (used for reverse zoom)
- * @return {Object} the current zoom center
- */
- getCurrentZoomCenter: function () {
- var offsetLeft = this.offset.x - this.initialOffset.x;
- var centerX = -1 * this.offset.x - offsetLeft / (1 / this.zoomFactor - 1);
- var offsetTop = this.offset.y - this.initialOffset.y;
- var centerY = -1 * this.offset.y - offsetTop / (1 / this.zoomFactor - 1);
- return {
- x: centerX,
- y: centerY
- };
- },
- /**
- * Returns the touches of an event relative to the container offset
- * @param event
- * @return array touches
- */
- getTouches: function (event) {
- var rect = this.container.getBoundingClientRect();
- var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
- var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
- var posTop = rect.top + scrollTop;
- var posLeft = rect.left + scrollLeft;
- return Array.prototype.slice.call(event.touches).map(function (touch) {
- return {
- x: touch.pageX - posLeft,
- y: touch.pageY - posTop,
- };
- });
- },
- /**
- * Animation loop
- * does not support simultaneous animations
- * @param duration
- * @param framefn
- * @param timefn
- * @param callback
- */
- animate: function (duration, framefn, timefn, callback) {
- var startTime = new Date().getTime(),
- renderFrame = (function () {
- if (!this.inAnimation) { return; }
- var frameTime = new Date().getTime() - startTime,
- progress = frameTime / duration;
- if (frameTime >= duration) {
- framefn(1);
- if (callback) {
- callback();
- }
- this.update();
- this.stopAnimation();
- this.update();
- } else {
- if (timefn) {
- progress = timefn(progress);
- }
- framefn(progress);
- this.update();
- requestAnimationFrame(renderFrame);
- }
- }).bind(this);
- this.inAnimation = true;
- requestAnimationFrame(renderFrame);
- },
- /**
- * Stops the animation
- */
- stopAnimation: function () {
- this.inAnimation = false;
- },
- /**
- * Swing timing function for animations
- * @param p
- * @return {Number}
- */
- swing: function (p) {
- return -Math.cos(p * Math.PI) / 2 + 0.5;
- },
- getContainerX: function () {
- return this.container.offsetWidth;
- },
- getContainerY: function () {
- return this.container.offsetHeight;
- },
- setContainerY: function (y) {
- return this.container.style.height = y + 'px';
- },
- unsetContainerY: function () {
- this.container.style.height = null;
- },
- /**
- * Creates the expected html structure
- */
- setupMarkup: function () {
- this.container = buildElement('<div class="pinch-zoom-container"></div>');
- this.el.parentNode.insertBefore(this.container, this.el);
- this.container.appendChild(this.el);
- this.container.style.overflow = 'hidden';
- this.container.style.position = 'relative';
- this.el.style.webkitTransformOrigin = '0% 0%';
- this.el.style.mozTransformOrigin = '0% 0%';
- this.el.style.msTransformOrigin = '0% 0%';
- this.el.style.oTransformOrigin = '0% 0%';
- this.el.style.transformOrigin = '0% 0%';
- this.el.style.position = 'absolute';
- },
- end: function () {
- this.hasInteraction = false;
- this.sanitize();
- this.update();
- },
- /**
- * Binds all required event listeners
- */
- bindEvents: function () {
- var self = this;
- detectGestures(this.container, this);
- this.resizeHandler = this.update.bind(this)
- window.addEventListener('resize', this.resizeHandler);
- Array.from(this.el.querySelectorAll('img')).forEach(function(imgEl) {
- imgEl.addEventListener('load', self.update.bind(self));
- });
- if (this.el.nodeName === 'IMG') {
- this.el.addEventListener('load', this.update.bind(this));
- }
- },
- /**
- * Updates the css values according to the current zoom factor and offset
- */
- update: function (event) {
- if (event && event.type === 'resize') {
- this.updateAspectRatio();
- this.setupOffsets();
- }
- if (event && event.type === 'load') {
- this.updateAspectRatio();
- this.setupOffsets();
- }
- if (this.updatePlanned) {
- return;
- }
- this.updatePlanned = true;
- window.setTimeout((function () {
- this.updatePlanned = false;
- var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor,
- offsetX = -this.offset.x / zoomFactor,
- offsetY = -this.offset.y / zoomFactor,
- transform3d = 'scale3d(' + zoomFactor + ', ' + zoomFactor + ',1) ' +
- 'translate3d(' + offsetX + 'px,' + offsetY + 'px,0px)',
- transform2d = 'scale(' + zoomFactor + ', ' + zoomFactor + ') ' +
- 'translate(' + offsetX + 'px,' + offsetY + 'px)',
- removeClone = (function () {
- if (this.clone) {
- this.clone.parentNode.removeChild(this.clone);
- delete this.clone;
- }
- }).bind(this);
- // Scale 3d and translate3d are faster (at least on ios)
- // but they also reduce the quality.
- // PinchZoom uses the 3d transformations during interactions
- // after interactions it falls back to 2d transformations
- if (!this.options.use2d || this.hasInteraction || this.inAnimation) {
- this.is3d = true;
- removeClone();
- this.el.style.webkitTransform = transform3d;
- this.el.style.mozTransform = transform2d;
- this.el.style.msTransform = transform2d;
- this.el.style.oTransform = transform2d;
- this.el.style.transform = transform3d;
- } else {
- // When changing from 3d to 2d transform webkit has some glitches.
- // To avoid this, a copy of the 3d transformed element is displayed in the
- // foreground while the element is converted from 3d to 2d transform
- if (this.is3d) {
- this.clone = this.el.cloneNode(true);
- this.clone.style.pointerEvents = 'none';
- this.container.appendChild(this.clone);
- window.setTimeout(removeClone, 200);
- }
- this.el.style.webkitTransform = transform2d;
- this.el.style.mozTransform = transform2d;
- this.el.style.msTransform = transform2d;
- this.el.style.oTransform = transform2d;
- this.el.style.transform = transform2d;
- this.is3d = false;
- }
- }).bind(this), 0);
- },
- /**
- * Enables event handling for gestures
- */
- enable: function() {
- this.enabled = true;
- },
- /**
- * Disables event handling for gestures
- */
- disable: function() {
- this.enabled = false;
- },
- /**
- * Unmounts the zooming container and global event listeners
- */
- destroy: function () {
- window.removeEventListener('resize', this.resizeHandler);
- if (this.container) {
- this.container.remove();
- this.container = null;
- }
- }
- };
- var detectGestures = function (el, target) {
- var interaction = null,
- fingers = 0,
- lastTouchStart = null,
- startTouches = null,
- setInteraction = function (newInteraction, event) {
- if (interaction !== newInteraction) {
- if (interaction && !newInteraction) {
- switch (interaction) {
- case "zoom":
- target.handleZoomEnd(event);
- break;
- case 'drag':
- target.handleDragEnd(event);
- break;
- }
- }
- switch (newInteraction) {
- case 'zoom':
- target.handleZoomStart(event);
- break;
- case 'drag':
- target.handleDragStart(event);
- break;
- }
- }
- interaction = newInteraction;
- },
- updateInteraction = function (event) {
- if (fingers === 2) {
- setInteraction('zoom');
- } else if (fingers === 1 && target.canDrag()) {
- setInteraction('drag', event);
- } else {
- setInteraction(null, event);
- }
- },
- targetTouches = function (touches) {
- return Array.from(touches).map(function (touch) {
- return {
- x: touch.pageX,
- y: touch.pageY
- };
- });
- },
- getDistance = function (a, b) {
- var x, y;
- x = a.x - b.x;
- y = a.y - b.y;
- return Math.sqrt(x * x + y * y);
- },
- calculateScale = function (startTouches, endTouches) {
- var startDistance = getDistance(startTouches[0], startTouches[1]),
- endDistance = getDistance(endTouches[0], endTouches[1]);
- return endDistance / startDistance;
- },
- cancelEvent = function (event) {
- event.stopPropagation();
- event.preventDefault();
- },
- detectDoubleTap = function (event) {
- var time = (new Date()).getTime();
- if (fingers > 1) {
- lastTouchStart = null;
- }
- if (time - lastTouchStart < 300) {
- cancelEvent(event);
- target.handleDoubleTap(event);
- switch (interaction) {
- case "zoom":
- target.handleZoomEnd(event);
- break;
- case 'drag':
- target.handleDragEnd(event);
- break;
- }
- } else {
- target.isDoubleTap = false;
- }
- if (fingers === 1) {
- lastTouchStart = time;
- }
- },
- firstMove = true;
- el.addEventListener('touchstart', function (event) {
- if(target.enabled) {
- firstMove = true;
- fingers = event.touches.length;
- detectDoubleTap(event);
- }
- }, { passive: false });
- el.addEventListener('touchmove', function (event) {
- if(target.enabled && !target.isDoubleTap) {
- if (firstMove) {
- updateInteraction(event);
- if (interaction) {
- cancelEvent(event);
- }
- startTouches = targetTouches(event.touches);
- } else {
- switch (interaction) {
- case 'zoom':
- if (startTouches.length == 2 && event.touches.length == 2) {
- target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches)));
- }
- break;
- case 'drag':
- target.handleDrag(event);
- break;
- }
- if (interaction) {
- cancelEvent(event);
- target.update();
- }
- }
- firstMove = false;
- }
- }, { passive: false });
- el.addEventListener('touchend', function (event) {
- if(target.enabled) {
- fingers = event.touches.length;
- updateInteraction(event);
- }
- });
- };
- return PinchZoom;
- };
- var PinchZoom = definePinchZoom();
- export default PinchZoom;
|