1、单页面,head标签里增加属性:

<meta name="viewport" 
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=3.5, user-scalable=yes">
/*注释*/
// width:控制 viewport 的大小,可以指定的一个值,如果 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)
// height:和 width 相对应,指定高度。
// initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
// maximum-scale:允许用户缩放到的最大比例。
// minimum-scale:允许用户缩放到的最小比例。
// user-scalable:用户是否可以手动缩放
// 

  

2、iframe引入的页面,想要缩放,这个标签就不生效了

安卓设备上,前端页面,手势缩放页面功能实现

 

 安卓设备上,前端页面,手势缩放页面功能实现

 

 

手势动作,iframe引入的页面并不能生效缩放动作。
iframe页面支持手势缩放的基本思路:
(1)监听手指缩放事件
(2)根据手指动作,判断缩放,并体现在页面上
(3)页面变化顺滑流畅的响应手指动作

 

(1)js监听事件:

//监听touchstart事件
    document.addEventListener('touchstart', function(e) {
        if (e.touches.length >= 2) { //判断是否有两个点在屏幕上
          
        } else {
         
        }
    }, false);
    //监听touchmove事件
    document.addEventListener('touchmove', function(e) {
       
        if (e.touches.length >= 2 && isDoubleTouch) { //手势事件
          
        } else if (isTouch) {
          
        }
    }, false);
    //监听touchend事件
    document.addEventListener('touchend', function(e) {
        if (isDoubleTouch) {
          
        };
    }, false);

安卓设备上,前端页面,手势缩放页面功能实现

 

 

 

 

 

动作:放-缩-放,缩放比例数值(scale)会根据手势动作变化

 

(2)根据手指动作,判断缩放,并体现在页面上:

方式1:

缩放事件响应时候,调节页面大小。初步实现方式是,缩放事件响应后,根据两指距离,调节scale属性,放大或缩小页面。

安卓设备上,前端页面,手势缩放页面功能实现

 

代码(错误示范):

//缩放比例
var scalingRatio = 1;
var DOM = $("#image");
function touchChange(n,gesturechange) {
    if(gesturechange.scale>1){
        // 放大
        if(scalingRatio<5)scalingRatio += gesturechange.scale/10
    }else{
        // 缩小
        if(scalingRatio>0.5)scalingRatio -= gesturechange.scale
    }
    DOM.css({'transform':'scale('+scalingRatio+')'})

    n+= "<br>"+gesturechange.scale;
    document.querySelector("#logs").innerHTML = n;
}

  

存在明显的问题:
[1] 两指放在屏幕上不动,两指间的距离有着细微的变化,也会修改scale的值,触发页面持续缩放,会在极短的时间内放的极大或缩的极小。
[2] 页面渲染速率比屏幕刷新率快,页面必然会出现卡顿

[3] 每次缩放操作,页面会定位回左上角。

 

方式2:

为解决以上问题:

[1] 随手势变化,实时监听中心坐标、两指见的距离

[2] 使用【scale3d】缩放,使用【translate3d、translate】移动页面

[3] 延时执行

 

该方案存在问题:

在低配置安卓分机上,卡顿严重!

具体实现方式:

(1) 响应touchmove事件,获取两指间的距离:

el.addEventListener('touchmove', function (event) {
    if(target.enabled) {
        if (firstMove) {
            updateInteraction(event);
            if (interaction) {
                cancelEvent(event);
            }
            startTouches = targetTouches(event.touches);
        } else {
            console.log('记录起始---startTouches--',JSON.stringify(startTouches),'\n','记录结束---endTouches--',JSON.stringify(targetTouches(event.touches)))
          
            switch (interaction) {
                case 'zoom':
                    target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches)));
                    break;
                case 'drag':
                    target.handleDrag(event);
                    break;
            }
            if (interaction) {
                cancelEvent(event);
                target.update();
            }
        }

        firstMove = false;
    }
});

  

// 记录手指坐标
targetTouches = function (touches) {
    return Array.prototype.slice.call(touches).map(function (touch) {
        return {
            x: touch.pageX,
            y: touch.pageY
        };
    });
},

  

//根据起止坐标计算距离变化
calculateScale = function (startTouches, endTouches) {
    var startDistance = getDistance(startTouches[0], startTouches[1]),
        endDistance = getDistance(endTouches[0], endTouches[1]);
    return endDistance / startDistance;
},
getDistance = function (a, b) {
    var x, y;
    x = a.x - b.x;
    y = a.y - b.y;
    return Math.sqrt(x * x + y * y);
},

  (2) 根据当前缩放系数和偏移量更新css值

update: function () {

    if (this.updatePlaned) {
        return;
    }
    this.updatePlaned = true;

    setTimeout((function () {
        this.updatePlaned = false;
        this.updateAspectRatio();

        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.remove();
                    delete this.clone;
                }
            }).bind(this);

        // PinchZoom在交互过程中使用3d变换
        // 互动后,它会退回到2D转换
        if (!this.options.use2d || this.hasInteraction || this.inAnimation) {
            this.is3d = true;
            removeClone();
            this.el.css({
                '-webkit-transform':  transform3d,
                '-o-transform':       transform2d,
                '-ms-transform':      transform2d,
                '-moz-transform':     transform2d,
                'transform':        transform3d
            });
        } else {

            // 从3d转换为2d转换时,Webkit会有一些故障。
            // 为避免这种情况,3D变换后的元素的副本会显示在
            // 元素从3d转换为2d转换时的前景
            if (this.is3d) {
                this.clone = this.el.clone();
                this.clone.css('pointer-events', 'none');
                this.clone.appendTo(this.container);
                setTimeout(removeClone, 200);
            }
            this.el.css({
                '-webkit-transform':  transform2d,
                '-o-transform':       transform2d,
                '-ms-transform':      transform2d,
                '-moz-transform':     transform2d,
                'transform':        transform2d
            });
            this.is3d = false;
        }
    }).bind(this), 0);
},

  

(3) 单指拖动,实时更新中心坐标,并根据容器限制滑动区域

/**
 * 计算当前偏移量和缩放系数的虚拟缩放中心
 * (used for reverse zoom)
 * @return {Object} the current zoom center
 */
getCurrentZoomCenter: function () {

    // uses following formula to calculate the zoom center x value
    // offset_left / offset_right = zoomcenter_x / (container_x - zoomcenter_x)
    var length = this.container[0].offsetWidth * this.zoomFactor,
        offsetLeft  = this.offset.x,
        offsetRight = length - offsetLeft -this.container[0].offsetWidth,
        widthOffsetRatio = offsetLeft / offsetRight,
        centerX = widthOffsetRatio * this.container[0].offsetWidth / (widthOffsetRatio + 1),

    // the same for the zoomcenter y
        height = this.container[0].offsetHeight * this.zoomFactor,
        offsetTop  = this.offset.y,
        offsetBottom = height - offsetTop - this.container[0].offsetHeight,
        heightOffsetRatio = offsetTop / offsetBottom,
        centerY = heightOffsetRatio * this.container[0].offsetHeight / (heightOffsetRatio + 1);

    // prevents division by zero
    if (offsetRight === 0) { centerX = this.container[0].offsetWidth; }
    if (offsetBottom === 0) { centerY = this.container[0].offsetHeight; }

    return {
        x: centerX,
        y: centerY
    };
},

  完整代码:

html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Pinchzoom.js Demo</title>

    <style type="text/css">
        div.pinch-zoom,
        div.pinch-zoom img{
            width: 100%;
            -webkit-user-drag: none;
        }

    </style>
    <link rel="stylesheet" href="style.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes" />

    <!-- pinchzoom requires: jquery -->
    <script type="text/javascript" src="../dependencies/jquery-1.7.2.min.js"></script>
    <script type="text/javascript" src="../src/pinchzoom.js"></script>
    <script type="text/javascript">
        $(function () {
            $('div.pinch-zoom').each(function () {
                new RTP.PinchZoom($(this), {});
            });
        })
    </script>
</head>
<body>

    <div class="page">
        <div class="pinch-zoom">
            <div class="d11" ></div>
            <div class="d11">1 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">2 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">3 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">4 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">5 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">6 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">7 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">8 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">9 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">10 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">11 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">12<img src="../../11371.jpg" alt=""></div>
            <div class="d11">13 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">14 <img src="../../11371.jpg" alt=""></div>
            <div class="d11"> 15<img src="../../11371.jpg" alt=""></div>
            <div class="d11"> 16<img src="../../11371.jpg" alt=""></div>
            <div class="d11"> 17<img src="../../11371.jpg" alt=""></div>
            <div class="d11">18 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">19 <img src="../../11371.jpg" alt=""></div>
            <div class="d11">20 <img src="../../11371.jpg" alt=""></div>
        </div>
    </div>

</body>
</html>

  JS:

  1 /*global jQuery, console, define, setTimeout, window*/
  2 (function () {
  3     'use strict';
  4     var definePinchZoom = function ($) {
  5 
  6         /**
  7          * Pinch zoom using jQuery
  8          * @version 0.0.2
  9          * @author Manuel Stofer <mst@rtp.ch>
 10          * @param el
 11          * @param options
 12          * @constructor
 13          */
 14         var PinchZoom = function (el, options) {
 15                 this.el = $(el);
 16                 this.zoomFactor = 1;
 17                 this.lastScale = 1;
 18                 this.offset = {
 19                     x: 0,
 20                     y: 0
 21                 };
 22                 this.options = $.extend({}, this.defaults, options);
 23                 this.setupMarkup();
 24                 this.bindEvents();
 25                 this.update();
 26                 // default enable.
 27                 this.enable();
 28 
 29             },
 30             sum = function (a, b) {
 31                 return a + b;
 32             },
 33             isCloseTo = function (value, expected) {
 34                 return value > expected - 0.01 && value < expected + 0.01;
 35             };
 36 
 37         PinchZoom.prototype = {
 38 
 39             defaults: {
 40                 tapZoomFactor: 2,
 41                 zoomOutFactor: 1.1,
 42                 animationDuration: 300,
 43                 maxZoom: 4,
 44                 minZoom: 0.5,
 45                 lockDragAxis: false,
 46                 use2d: true,
 47                 zoomStartEventName: 'pz_zoomstart',
 48                 zoomEndEventName: 'pz_zoomend',
 49                 dragStartEventName: 'pz_dragstart',
 50                 dragEndEventName: 'pz_dragend',
 51                 doubleTapEventName: 'pz_doubletap'
 52             },
 53 
 54             /**
 55              * Event handler for 'dragstart'
 56              * @param event
 57              */
 58             handleDragStart: function (event) {
 59                 this.el.trigger(this.options.dragStartEventName);
 60                 this.stopAnimation();
 61                 this.lastDragPosition = false;
 62                 this.hasInteraction = true;
 63                 this.handleDrag(event);
 64             },
 65 
 66             /**
 67              * Event handler for 'drag'
 68              * @param event
 69              */
 70             handleDrag: function (event) {
 71                 if (this.zoomFactor > 1.0) {
 72                     var touch = this.getTouches(event)[0];
 73                     this.drag(touch, this.lastDragPosition);
 74                     // this.offset = this.sanitizeOffset(this.offset);
 75                     this.lastDragPosition = touch;
 76                 }
 77             },
 78 
 79             handleDragEnd: function () {
 80                 this.el.trigger(this.options.dragEndEventName);
 81                 this.end();
 82             },
 83 
 84             /**
 85              * Event handler for 'zoomstart'
 86              * @param event
 87              */
 88             handleZoomStart: function (event) {
 89                 this.el.trigger(this.options.zoomStartEventName);
 90                 this.stopAnimation();
 91                 this.lastScale = 1;
 92                 this.nthZoom = 0;
 93                 this.lastZoomCenter = false;
 94                 this.hasInteraction = true;
 95             },
 96 
 97             /**
 98              * 缩放的事件处理程序
 99              * @param event
100              */
101             handleZoom: function (event, newScale) {
102                 // a relative scale factor is used
103                 var touchCenter = this.getTouchCenter(this.getTouches(event)),
104                     scale = newScale / this.lastScale;
105                 this.lastScale = newScale;
106 
107                 // 第一次触摸事件由于不精确而被丢弃
108                 this.nthZoom += 1;
109                 if (this.nthZoom > 3) {
110                     this.scale(scale, touchCenter);
111                     this.drag(touchCenter, this.lastZoomCenter);
112                 }
113                 this.lastZoomCenter = touchCenter;
114             },
115 
116             handleZoomEnd: function () {
117                 this.el.trigger(this.options.zoomEndEventName);
118                 this.end();
119             },
120 
121             /**
122              * Event handler for 'doubletap'
123              * @param event
124              */
125             handleDoubleTap: function (event) {
126                 var center = this.getTouches(event)[0],
127                     zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor,
128                     startZoomFactor = this.zoomFactor,
129                     updateProgress = (function (progress) {
130                         this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
131                     }).bind(this);
132 
133                 if (this.hasInteraction) {
134                     return;
135                 }
136                 if (startZoomFactor > zoomFactor) {
137                     center = this.getCurrentZoomCenter();
138                 }
139 
140                 this.animate(this.options.animationDuration, updateProgress, this.swing);
141                 this.el.trigger(this.options.doubleTapEventName);
142             },
143 
144             /**
145              * 偏移的最大值/最小值
146              * @param offset
147              * @return {Object} the sanitized offset
148              */
149             sanitizeOffset: function (offset) {
150                 var maxX = (this.zoomFactor - 1) * this.getContainerX(),
151                     maxY = (this.zoomFactor - 1) * this.getContainerY(),
152                     maxOffsetX = Math.max(maxX, 0),
153                     maxOffsetY = Math.max(maxY, 0),
154                     minOffsetX = Math.min(maxX, 0),
155                     minOffsetY = Math.min(maxY, 0),
156                     RY = offset.y
157                 ;
158 
159                 if(offset.y < 0 ){
160                     RY = Math.min(Math.max(offset.y, minOffsetY), maxOffsetY)
161                 }
162                 else if(offset.y > this.getContainerY()){
163                     RY = this.getContainerY()
164                 }
165 
166                 console.log(
167                     'offset:',offset,'\n',
168                     'maxX:',maxX,'\n',
169                     'maxY:',maxY,'\n',
170                     '容器高度:',this.getContainerY(),'\n'
171                 )
172 
173                 return {
174                     x: Math.min(Math.max(offset.x, minOffsetX), maxOffsetX),
175                     y: RY
176                 };
177             },
178 
179             /**
180              * Scale to a specific zoom factor (not relative)
181              * @param zoomFactor
182              * @param center
183              */
184             scaleTo: function (zoomFactor, center) {
185                 this.scale(zoomFactor / this.zoomFactor, center);
186             },
187 
188             /**
189              * 从指定的中心缩放元素
190              * @param scale
191              * @param center
192              */
193             scale: function (scale, center) {
194                 scale = this.scaleZoomFactor(scale);
195                 this.addOffset({
196                     x: (scale - 1) * (center.x + this.offset.x),
197                     y: (scale - 1) * (center.y + this.offset.y)
198                 });
199             },
200 
201             /**
202              * 相对于当前状态缩放缩放系数
203              * @param scale
204              * @return the actual scale (can differ because of max min zoom factor)
205              */
206             scaleZoomFactor: function (scale) {
207                 var originalZoomFactor = this.zoomFactor;
208                 this.zoomFactor *= scale;
209                 this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom));
210                 return this.zoomFactor / originalZoomFactor;
211             },
212 
213             /**
214              * 拖动元素
215              * @param center
216              * @param lastCenter
217              */
218             drag: function (center, lastCenter) {
219                 // console.log('拖动事件参数:',center, lastCenter,'\nthis.options',this.options)
220                 if (lastCenter) {
221                   if(this.options.lockDragAxis) {
222                     // 将滚动条锁定到更改最多的位置
223                     if(Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) {
224                       this.addOffset({
225                         x: -(center.x - lastCenter.x),
226                         y: 0
227                       });
228                     }
229                     else {
230                       this.addOffset({
231                         y: -(center.y - lastCenter.y),
232                         x: 0
233                       });
234                     }
235                   }
236                   else {
237                     this.addOffset({
238                       y: -(center.y - lastCenter.y),
239                       x: -(center.x - lastCenter.x)
240                     });
241                   }
242                 }
243             },
244 
245             /**
246              * Calculates the touch center of multiple touches
247              * @param touches
248              * @return {Object}
249              */
250             getTouchCenter: function (touches) {
251                 return this.getVectorAvg(touches);
252             },
253 
254             /**
255              * Calculates the average of multiple vectors (x, y values)
256              */
257             getVectorAvg: function (vectors) {
258                 return {
259                     x: vectors.map(function (v) { return v.x; }).reduce(sum) / vectors.length,
260                     y: vectors.map(function (v) { return v.y; }).reduce(sum) / vectors.length
261                 };
262             },
263 
264             /**
265              * 添加偏移
266              * @param offset the offset to add
267              * @return return true when the offset change was accepted
268              */
269             addOffset: function (offset) {
270                 this.offset = {
271                     x: this.offset.x + offset.x,
272                     y: this.offset.y + offset.y
273                 };
274             },
275 
276             sanitize: function () {
277                 if (this.zoomFactor < this.options.zoomOutFactor) {
278                     this.zoomOutAnimation();
279                 } else if (this.isInsaneOffset(this.offset)) {
280                     this.sanitizeOffsetAnimation();
281                 }
282             },
283 
284             /**
285              * 检查当前缩放倍数的偏移量是否正确
286              * @param offset
287              * @return {Boolean}
288              */
289             isInsaneOffset: function (offset) {
290                 var sanitizedOffset = this.sanitizeOffset(offset);
291                 return sanitizedOffset.x !== offset.x ||
292                     sanitizedOffset.y !== offset.y;
293             },
294 
295             /**
296              * 创建移动到合理偏移的动画
297              */
298             sanitizeOffsetAnimation: function () {
299                 console.log('创建移动到合理偏移的动画')
300                 var targetOffset = this.sanitizeOffset(this.offset),
301                     startOffset = {
302                         x: this.offset.x,
303                         y: this.offset.y
304                     },
305                     updateProgress = (function (progress) {
306                         this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x);
307                         this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y);
308                         this.update();
309                     }).bind(this);
310 
311                 this.animate(
312                     this.options.animationDuration,
313                     updateProgress,
314                     this.swing
315                 );
316             },
317 
318             /**
319              * 缩放回原始位置,
320              * (no offset and zoom factor 1)
321              */
322             zoomOutAnimation: function () {
323                 var startZoomFactor = this.zoomFactor,
324                     zoomFactor = 1,
325                     center = this.getCurrentZoomCenter(),
326                     updateProgress = (function (progress) {
327                         this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center);
328                     }).bind(this);
329 
330                 this.animate(
331                     this.options.animationDuration,
332                     updateProgress,
333                     this.swing
334                 );
335             },
336 
337             /**
338              * 更新宽高比
339              */
340             updateAspectRatio: function () {
341                 this.setContainerY(this.getContainerX() / this.getAspectRatio());
342             },
343 
344             /**
345              *计算初始缩放系数(以使元素适合容器)
346              * @return the initial zoom factor
347              */
348             getInitialZoomFactor: function () {
349                 // use .offsetWidth instead of width()
350                 // because jQuery-width() return the original width but Zepto-width() will calculate width with transform.
351                 // the same as .height()
352                 return this.container[0].offsetWidth / this.el[0].offsetWidth;
353             },
354 
355             /**
356              * 计算元素的长宽比
357              * @return the aspect ratio
358              */
359             getAspectRatio: function () {
360                 return this.el[0].offsetWidth / this.el[0].offsetHeight;
361             },
362 
363             /**
364              * 计算当前偏移量和缩放系数的虚拟缩放中心
365              * (used for reverse zoom)
366              * @return {Object} the current zoom center
367              */
368             getCurrentZoomCenter: function () {
369 
370                 // uses following formula to calculate the zoom center x value
371                 // offset_left / offset_right = zoomcenter_x / (container_x - zoomcenter_x)
372                 var length = this.container[0].offsetWidth * this.zoomFactor,
373                     offsetLeft  = this.offset.x,
374                     offsetRight = length - offsetLeft -this.container[0].offsetWidth,
375                     widthOffsetRatio = offsetLeft / offsetRight,
376                     centerX = widthOffsetRatio * this.container[0].offsetWidth / (widthOffsetRatio + 1),
377 
378                 // the same for the zoomcenter y
379                     height = this.container[0].offsetHeight * this.zoomFactor,
380                     offsetTop  = this.offset.y,
381                     offsetBottom = height - offsetTop - this.container[0].offsetHeight,
382                     heightOffsetRatio = offsetTop / offsetBottom,
383                     centerY = heightOffsetRatio * this.container[0].offsetHeight / (heightOffsetRatio + 1);
384 
385                 // prevents division by zero
386                 if (offsetRight === 0) { centerX = this.container[0].offsetWidth; }
387                 if (offsetBottom === 0) { centerY = this.container[0].offsetHeight; }
388 
389                 return {
390                     x: centerX,
391                     y: centerY
392                 };
393             },
394 
395             canDrag: function () {
396                 return !isCloseTo(this.zoomFactor, 1);
397             },
398 
399             /**
400              * Returns the touches of an event relative to the container offset
401              * @param event
402              * @return array touches
403              */
404             getTouches: function (event) {
405                 var position = this.container.offset();
406                 return Array.prototype.slice.call(event.touches).map(function (touch) {
407                     return {
408                         x: touch.pageX - position.left,
409                         y: touch.pageY - position.top
410                     };
411                 });
412             },
413 
414             /**
415              * Animation loop
416              * does not support simultaneous animations
417              * @param duration
418              * @param framefn
419              * @param timefn
420              * @param callback
421              */
422             animate: function (duration, framefn, timefn, callback) {
423                 var startTime = new Date().getTime(),
424                     renderFrame = (function () {
425                         if (!this.inAnimation) { return; }
426                         var frameTime = new Date().getTime() - startTime,
427                             progress = frameTime / duration;
428                         if (frameTime >= duration) {
429                             framefn(1);
430                             if (callback) {
431                                 callback();
432                             }
433                             this.update();
434                             this.stopAnimation();
435                             this.update();
436                         } else {
437                             if (timefn) {
438                                 progress = timefn(progress);
439                             }
440                             framefn(progress);
441                             this.update();
442                             requestAnimationFrame(renderFrame);
443                         }
444                     }).bind(this);
445                 this.inAnimation = true;
446                 requestAnimationFrame(renderFrame);
447             },
448 
449             /**
450              * Stops the animation
451              */
452             stopAnimation: function () {
453                 this.inAnimation = false;
454             },
455 
456             /**
457              * Swing timing function for animations
458              * @param p
459              * @return {Number}
460              */
461             swing: function (p) {
462                 return -Math.cos(p * Math.PI) / 2  + 0.5;
463             },
464 
465             getContainerX: function () {
466                 return this.container[0].offsetWidth;
467             },
468 
469             getContainerY: function () {
470                 return this.container[0].offsetHeight;
471             },
472 
473             setContainerY: function (y) {
474                 return this.container.height(y);
475             },
476 
477             /**
478              * 创建预期的html结构
479              */
480             setupMarkup: function () {
481                 this.container = $('<div class="pinch-zoom-container"></div>');
482                 this.el.before(this.container);
483                 this.container.append(this.el);
484 
485                 this.container.css({
486                     'overflow': 'hidden',
487                     'position': 'relative'
488                 });
489 
490                 // Zepto无法识别“ webkitTransform ..”样式
491                 this.el.css({
492                     '-webkit-transform-origin': '0% 0%',
493                     '-moz-transform-origin': '0% 0%',
494                     '-ms-transform-origin': '0% 0%',
495                     '-o-transform-origin': '0% 0%',
496                     'transform-origin': '0% 0%',
497                     'position': 'absolute'
498                 });
499             },
500 
501             end: function () {
502                 this.hasInteraction = false;
503                 this.sanitize();
504                 this.update();
505             },
506 
507             /**
508              * 绑定所有必需的事件侦听器
509              */
510             bindEvents: function () {
511                 detectGestures(this.container.get(0), this);
512                 // Zepto and jQuery both know about `on`
513                 $(window).on('resize', this.update.bind(this));
514                 $(this.el).find('img').on('load', this.update.bind(this));
515             },
516 
517             /**
518              * 根据当前缩放系数和偏移量更新css值
519              */
520             update: function () {
521 
522                 if (this.updatePlaned) {
523                     return;
524                 }
525                 this.updatePlaned = true;
526 
527                 setTimeout((function () {
528                     this.updatePlaned = false;
529                     this.updateAspectRatio();
530 
531                     var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor,
532                         offsetX = -this.offset.x / zoomFactor,
533                         offsetY = -this.offset.y / zoomFactor,
534                         transform3d =   'scale3d('     + zoomFactor + ', '  + zoomFactor + ',1) ' +
535                             'translate3d(' + offsetX    + 'px,' + offsetY    + 'px,0px)',
536                         transform2d =   'scale('       + zoomFactor + ', '  + zoomFactor + ') ' +
537                             'translate('   + offsetX    + 'px,' + offsetY    + 'px)',
538                         removeClone = (function () {
539                             if (this.clone) {
540                                 this.clone.remove();
541                                 delete this.clone;
542                             }
543                         }).bind(this);
544 
545                     var n = "第一行 - this.zoomFactor:"+this.zoomFactor+"<br>"+
546                         "|||zoomFactor:"+zoomFactor+"<br>"+
547                         "|||transform2d:"+transform2d+"<br>"+
548                         "|||transform3d:"+transform3d+"<br>"+
549                         "|||this.lastScale:"+this.lastScale+"<br>"+
550                         "|||this.lastZoomCenter:"+JSON.stringify(this.lastZoomCenter)+"<br>"+
551                         "|||this.offset:"+JSON.stringify(this.offset)+"<br>"+
552                         "|||this.nthZoom:"+this.nthZoom+"<br>"+
553                         "|||this.zoomFactor:"+this.zoomFactor+"<br>"+
554                         "|||this.updatePlaned:"+this.updatePlaned
555                     ;
556                     document.getElementById('logs').innerHTML = n
557                     /*console.log(
558                         'this.offset:',JSON.stringify(this.offset),
559                         '\n',
560                         'offsetX:',offsetX,
561                         '\n',
562                         'offsetY:',offsetY,
563                         '\n',
564                         'transform2d:',transform2d,
565                         '\n',
566                         'transform3d:',transform3d,
567                         '\n',
568 
569                         ''
570                     )*/
571 
572                     // Scale 3d和translate3d更快(至少在iOS上)
573                     // 但它们也会降低质量
574                     // PinchZoom在交互过程中使用3d变换
575                     // 互动后,它会退回到2D转换
576                     if (!this.options.use2d || this.hasInteraction || this.inAnimation) {
577                         this.is3d = true;
578                         removeClone();
579                         this.el.css({
580                             '-webkit-transform':  transform3d,
581                             '-o-transform':       transform2d,
582                             '-ms-transform':      transform2d,
583                             '-moz-transform':     transform2d,
584                             'transform':        transform3d
585                         });
586                     } else {
587 
588                         // 从3d转换为2d转换时,Webkit会有一些故障。
589                         // 为避免这种情况,3D变换后的元素的副本会显示在
590                         // 元素从3d转换为2d转换时的前景
591                         if (this.is3d) {
592                             this.clone = this.el.clone();
593                             this.clone.css('pointer-events', 'none');
594                             this.clone.appendTo(this.container);
595                             setTimeout(removeClone, 200);
596                         }
597                         this.el.css({
598                             '-webkit-transform':  transform2d,
599                             '-o-transform':       transform2d,
600                             '-ms-transform':      transform2d,
601                             '-moz-transform':     transform2d,
602                             'transform':        transform2d
603                         });
604                         this.is3d = false;
605                     }
606                 }).bind(this), 0);
607             },
608 
609             /**
610              * Enables event handling for gestures
611              */
612             enable: function() {
613               this.enabled = true;
614             },
615 
616             /**
617              * Disables event handling for gestures
618              */
619             disable: function() {
620               this.enabled = false;
621             }
622         };
623 
624         var detectGestures = function (el, target) {
625             var interaction = null,
626                 fingers = 0,
627                 lastTouchStart = null,
628                 startTouches = null,
629 
630                 setInteraction = function (newInteraction, event) {
631                     if (interaction !== newInteraction) {
632                         // console.log('interaction && !newInteraction: ',interaction,newInteraction,interaction && !newInteraction)
633                         if (interaction && !newInteraction) {
634                             switch (interaction) {
635                                 case "zoom":
636                                     target.handleZoomEnd(event);
637                                     break;
638                                 case 'drag':
639                                     target.handleDragEnd(event);
640                                     break;
641                             }
642                         }
643 
644                         switch (newInteraction) {
645                             case 'zoom':
646                                 target.handleZoomStart(event);
647                                 break;
648                             case 'drag':
649                                 target.handleDragStart(event);
650                                 break;
651                         }
652                     }
653                     interaction = newInteraction;
654                 },
655 
656                 updateInteraction = function (event) {
657                     if (fingers === 2) {
658                         setInteraction('zoom');
659                     } else if (fingers === 1 && target.canDrag()) {
660                         setInteraction('drag', event);
661                     } else {
662                         setInteraction(null, event);
663                     }
664                 },
665 
666                 targetTouches = function (touches) {
667                     return Array.prototype.slice.call(touches).map(function (touch) {
668                         return {
669                             x: touch.pageX,
670                             y: touch.pageY
671                         };
672                     });
673                 },
674 
675                 getDistance = function (a, b) {
676                     var x, y;
677                     x = a.x - b.x;
678                     y = a.y - b.y;
679                     return Math.sqrt(x * x + y * y);
680                 },
681 
682                 calculateScale = function (startTouches, endTouches) {
683                     var startDistance = getDistance(startTouches[0], startTouches[1]),
684                         endDistance = getDistance(endTouches[0], endTouches[1]);
685                     return endDistance / startDistance;
686                 },
687 
688                 cancelEvent = function (event) {
689                     event.stopPropagation();
690                     event.preventDefault();
691                 },
692 
693                 detectDoubleTap = function (event) {
694                     var time = (new Date()).getTime();
695 
696                     if (fingers > 1) {
697                         lastTouchStart = null;
698                     }
699 
700                     if (time - lastTouchStart < 300) {
701                         cancelEvent(event);
702 
703                         target.handleDoubleTap(event);
704                         switch (interaction) {
705                             case "zoom":
706                                 target.handleZoomEnd(event);
707                                 break;
708                             case 'drag':
709                                 target.handleDragEnd(event);
710                                 break;
711                         }
712                     }
713 
714                     if (fingers === 1) {
715                         lastTouchStart = time;
716                     }
717                 },
718                 firstMove = true;
719 
720             el.addEventListener('touchstart', function (event) {
721                 if(target.enabled) {
722                     firstMove = true;
723                     fingers = event.touches.length;
724                     detectDoubleTap(event);
725                 }
726             });
727 
728             el.addEventListener('touchmove', function (event) {
729                 // console.log('target.enabled: ',target.enabled,'\nfirstMove:',firstMove,'\ninteraction:',interaction)
730                 if(target.enabled) {
731                     if (firstMove) {
732                         /*console.log('单指事件\n',
733                             '器使坐标startTouches:',
734                             startTouches,
735                             '\n',
736                             '终点坐标:',
737                             targetTouches(event.touches)
738                         )*/
739 
740                         updateInteraction(event);
741                         if (interaction) {
742                             cancelEvent(event);
743                         }
744                         startTouches = targetTouches(event.touches);
745                     } else {
746                         /*console.log('双指事件\n根据起止坐标计算距离变化--',
747                             calculateScale(startTouches, targetTouches(event.touches)),
748                             '\n',
749                             '器使坐标startTouches:',
750                             startTouches,
751                             '\n',
752                             '终点坐标:',
753                             targetTouches(event.touches)
754                         )*/
755                         switch (interaction) {
756                             case 'zoom':
757                                 target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches)));
758                                 break;
759                             case 'drag':
760                                 target.handleDrag(event);
761                                 break;
762                         }
763                         if (interaction) {
764                             cancelEvent(event);
765                             target.update();
766                         }
767                     }
768 
769                     firstMove = false;
770                 }
771             });
772 
773             el.addEventListener('touchend', function (event) {
774                 if(target.enabled) {
775                     fingers = event.touches.length;
776                     updateInteraction(event);
777                 }
778             });
779         };
780 
781         return PinchZoom;
782     };
783 
784     if (typeof define !== 'undefined' && define.amd) {
785         define(['jquery'], function ($) {
786             return definePinchZoom($);
787         });
788     } else {
789         window.RTP = window.RTP || {};
790         window.RTP.PinchZoom = definePinchZoom(window.$);
791     }
792 }).call(this);
View Code

相关文章: