Bootstrap

移动端 手势缩放拖拽功能实现

 

效果图:

 

 

实现方式:

基于 PinchZoom.js Version: 2.3.4

修改源码添加如下功能

1、增加初始化居中功能
2、增加初始化缩放比功能

TODO 

希望增加惯性滑动
 

使用示例:

<template>
<div>
  <div :style="'height:'+windowHeight+'px'">
     <div  id="map_img">
       <img :src="mapImg"  />
     </div>
  </div>
</div>
</template>
import mapImg from '@/assets/images/map.png';
import PinchZoom from '@/utils/pinch-zoom';
export default {
name: "mapIndex",
  data(){
    return{
      mapImg,
      windowHeight: window.innerHeight,
    }
  },
  mounted() {
    let myElement = document.getElementById("map_img");
    let pz = new PinchZoom(myElement, {
      minZoom: 4,
      maxZoom: 12,
      zoomFactor:4,
    });
  }
}
#map_img{
   img{
     width: 750px;
   }
}

 

API :

tapZoomFactor:双击放大的缩放系数。(默认2)
zoomOutFactor:当缩放系数低于此值时,调整为原始大小。(默认值为1.3)
animationDuration:动画持续时间(以毫秒为单位)。(默认为300)
maxZoom:最大缩放系数。(默认为4)
minZoom:最小缩放系数。(预设0.5)
draggableUnzoomed:即使图像未缩放,也可以捕获拖动事件。(默认为true)
                    (使用“ false”允许其他库(例如,滑动)拾取拖动事件)
lockDragAxis:将元素的平移锁定到单个轴。(默认为false)
setOffsetsOnce:仅计算一次偏移(容器内的图像位置)。(默认为false)
                    (使用true可以在连续的load和resize上保持偏移量)
use2d:空闲时回退到2D变换。(默认为true)
                    (真实值仍将在动画过程中使用3D变换)
verticalPadding:应用于图像周围的垂直填充。(默认为0)
horizo​​ntalPadding:在图像周围应用的水平填充。(默认为0)
 
zoomFactor:自定义参数 图像初始化缩放比。(默认为1)
 
onZoomStart:zoomstart事件的回调(参数:Pinchzoom对象,Event事件)(默认为null)
onZoomEnd:zoomend事件的回调(参数:Pinchzoom对象,Event事件)(默认为null)
onZoomUpdate:zoomupdate事件的回调(参数:Pinchzoom对象,Event事件)(默认为null)
onDragStart:dragstart事件的回调(参数:Pinchzoom对象,Event事件)(默认为null)
onDragEnd:Dragend事件的回调(参数:Pinchzoom对象,Event事件)(默认为null)
onDragUpdate:dragupdate事件的回调(参数:Pinchzoom对象,Event事件)(默认为null)
onDoubleTap:双击事件的回调(参数:Pinchzoom对象,事件事件)(默认为null)

 

 PinchZoom.js 源码: 复制下来保存为js文件 import PinchZoom from 'pinch-zoom.js'; 引入即可 ,如上示例

/**
 * @author <[email protected]>
 * @date 2020-11-11 18:08:50
 * @description  基于 PinchZoom.js Version: 2.3.4
 * 1、增加初始化居中功能
 * 2、增加初始化缩放比功能
 * TODO 缺少惯性滑动
 * @param
 * @return
 */

// 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.zoomFactor=this.options.zoomFactor;
            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,
            zoomFactor:1,
            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);

            window.addEventListener('resize', this.update.bind(this));
            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));
            }
        },

        isFirstLoad:true,

        /**
         * Updates the css values according to the current zoom factor and offset
         */
        update: function (event) {
            if (this.updatePlaned) {
                return;
            }
            this.updatePlaned = true;

            window.setTimeout((function () {

                this.updatePlaned = false;

                if (event && event.type === 'resize') {
                    this.updateAspectRatio();
                    this.setupOffsets();
                }

                if (event && event.type === 'load') {
                  this.updateAspectRatio();
                  this.setupOffsets();
                }

                var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor,offsetX,offsetY;
                if(this.container.offsetHeight > 0 && this.isFirstLoad){
                  //第一次加载默认居中
                  offsetX = -(this.el.offsetWidth*this.zoomFactor/2-(window.innerWidth/2))/this.zoomFactor;
                    offsetY = -(this.el.offsetHeight*this.zoomFactor/2-(window.innerHeight/2))/this.zoomFactor;
                    this.isFirstLoad=false;
                  this.offset.x= -offsetX*this.zoomFactor;
                  this.offset.y= -offsetY*this.zoomFactor;
                }else{
                  offsetX = -this.offset.x / zoomFactor;
                  offsetY = -this.offset.y / zoomFactor;
                }

                var 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;
        }
    };

    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);
            }
        });

        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;
            }
        });

        el.addEventListener('touchend', function (event) {
            if(target.enabled) {
                fingers = event.touches.length;
                updateInteraction(event);
            }
        });
    };

    return PinchZoom;
};

var PinchZoom = definePinchZoom();

export default PinchZoom;

 

;