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