标签:ext 现在 amp padding exp wing oop custom inter
<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:用户是否可以手动缩放 //
//监听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; }
方式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" id="logs" style="min-height: 160px;background: #bcffe1;position: fixed;width: 500px;color:red;font-size:2vw;height: auto;"></div> <div class="d11">1 <img src="../../11371.jpg" ></div> <div class="d11">2 <img src="../../11371.jpg" ></div> <div class="d11">3 <img src="../../11371.jpg" ></div> <div class="d11">4 <img src="../../11371.jpg" ></div> <div class="d11">5 <img src="../../11371.jpg" ></div> <div class="d11">6 <img src="../../11371.jpg" ></div> <div class="d11">7 <img src="../../11371.jpg" ></div> <div class="d11">8 <img src="../../11371.jpg" ></div> <div class="d11">9 <img src="../../11371.jpg" ></div> <div class="d11">10 <img src="../../11371.jpg" ></div> <div class="d11">11 <img src="../../11371.jpg" ></div> <div class="d11">12<img src="../../11371.jpg" ></div> <div class="d11">13 <img src="../../11371.jpg" ></div> <div class="d11">14 <img src="../../11371.jpg" ></div> <div class="d11"> 15<img src="../../11371.jpg" ></div> <div class="d11"> 16<img src="../../11371.jpg" ></div> <div class="d11"> 17<img src="../../11371.jpg" ></div> <div class="d11">18 <img src="../../11371.jpg" ></div> <div class="d11">19 <img src="../../11371.jpg" ></div> <div class="d11">20 <img src="../../11371.jpg" ></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);
页面效果:
方式3:
[1] 同样的随手势变化,实时监听中心坐标、两指见的距离,显示改变的百分比
[2] 手指抬起后,获取最终变化的百分比,计算页面样式重绘
该方案存在问题:
安卓分机上,获取手指离开事件,有几率获取不到。
重新计算样式,触发浏览器重绘动作,数据量较大的情况下,时间会比较慢。
完整代码:
HTML:
<div id="showProportion" style="position: fixed;z-index: 99999;width: 16vw;height: 16vh;line-height: 16vh;left: 42vw;top:33vh;background: rgba(0,0,0,.5);text-align: center;color: #fff;font-size: 8vh;display: none;">100%</div>
JS:
1 (function(){ 2 var isTouch = false; 3 var isDoubleTouch = false; //是否为多触点 4 var start = []; //存放触点坐标 5 var now, delta; //当前时间,两次触发事件时间差 6 var timer = null; //计时器,触发单击事件 7 var startPosition, movePosition, endPosition; //滑动起点,移动,结束点坐标 8 //事件声明 9 var gesturestart = new CustomEvent(‘gesturestart‘); 10 var gesturechange = new CustomEvent(‘gesturechange‘); 11 var gestureend = new CustomEvent(‘gestureend‘); 12 var swipeMove = new CustomEvent(‘swipeMove‘); 13 var doubleTouch = new CustomEvent("doubleTouch"); 14 var oneTouch = new CustomEvent("oneTouch"); 15 16 //监听touchstart事件 17 document.addEventListener(‘touchstart‘, function(e) { 18 if (e.touches.length >= 2) { //判断是否有两个点在屏幕上 19 isDoubleTouch = true; 20 start = e.touches; //得到第一组两个点 21 var screenMinPoint = getMidpoint(start[0], start[1]); //获取两个触点中心坐标 22 gesturestart.midPoint = [screenMinPoint[0] - e.target.offsetLeft, screenMinPoint[1] - e.target.offsetTop]; //获取中心点坐标相对目标元素坐标 23 e.target.dispatchEvent(gesturestart); 24 } else { 25 delta = Date.now() - now; //计算两次点击时间差 26 now = Date.now(); 27 startPosition = [e.touches[0].pageX, e.touches[0].pageY]; 28 if (delta > 0 && delta <= 250) { //双击事件 29 clearTimeout(timer); 30 31 doubleTouch.position = [e.touches[0].pageX - e.target.offsetLeft, e.touches[0].pageY - e.target.offsetTop]; 32 e.target.dispatchEvent(doubleTouch); 33 } else { //滑动事件 34 timer = setTimeout(function(){ 35 e.target.dispatchEvent(oneTouch);//单击事件 36 },450) 37 } 38 isTouch = true; 39 40 } 41 }, false); 42 //监听touchmove事件 43 document.addEventListener(‘touchmove‘, function(e) { 44 clearTimeout(timer); 45 46 if (e.touches.length >= 2 && isDoubleTouch) { //手势事件 47 var now = e.touches; //得到第二组两个点 48 var scale = getDistance(now[0], now[1]) / getDistance(start[0], start[1]); //得到缩放比例 49 var rotation = getAngle(now[0], now[1]) - getAngle(start[0], start[1]); //得到旋转角度差 50 gesturechange.scale = scale.toFixed(2); 51 gesturechange.rotation = rotation.toFixed(2); 52 e.target.dispatchEvent(gesturechange); 53 54 TouchChange.move(gesturechange); 55 } else if (isTouch) { 56 movePosition = [e.touches[0].pageX, e.touches[0].pageY]; 57 endPosition = movePosition; 58 movePosition = [movePosition[0] - startPosition[0], movePosition[1] - startPosition[1]]; 59 startPosition = [e.touches[0].pageX, e.touches[0].pageY]; 60 swipeMove.distance =[movePosition[0].toFixed(2) , movePosition[1].toFixed(2)]; 61 e.target.dispatchEvent(swipeMove); 62 } 63 }, false); 64 //监听touchend事件 65 document.addEventListener(‘touchend‘, function(e) { 66 if (isDoubleTouch) { 67 isDoubleTouch = false; 68 gestureend.position = endPosition; 69 e.target.dispatchEvent(gestureend); 70 71 TouchChange.end(gesturechange); 72 }; 73 }, false); 74 /* 75 * 两点的距离 76 */ 77 function getDistance(p1, p2) { 78 var x = p2.pageX - p1.pageX, 79 y = p2.pageY - p1.pageY; 80 return Math.sqrt((x * x) + (y * y)); 81 }; 82 /* 83 * 两点的夹角 84 */ 85 function getAngle(p1, p2) { 86 var x = p1.pageX - p2.pageX, 87 y = p1.pageY - p2.pageY; 88 return Math.atan2(y, x) * 180 / Math.PI; 89 }; 90 /* 91 * 获取中点 92 */ 93 function getMidpoint(p1, p2) { 94 var x = (p1.pageX + p2.pageX) / 2, 95 y = (p1.pageY + p2.pageY) / 2; 96 return [x, y]; 97 } 98 99 })() 100 101 targetTouches = function (touches) { 102 return Array.prototype.slice.call(touches).map(function (touch) { 103 return { 104 x: touch.pageX, 105 y: touch.pageY 106 }; 107 }); 108 }; 109 110 111 var TouchChange = { 112 scalingRatio: 1 , // 初始比例 113 DOM : $(".container"), 114 /** 115 * 手指移动事件 116 * @param gesturechange 117 */ 118 move: function (gesturechange) { 119 var _this = this; 120 var scale = parseInt(gesturechange.scale * 100); 121 if(scale > 500){ 122 scale = 500; 123 }else if (scale < 50){ 124 scale = 50; 125 } 126 scale += ‘%‘; 127 console.log(‘缩放比例:‘,scale) 128 129 $("#showProportion").show(); 130 $("#showProportion").html(scale); 131 }, 132 /** 133 * 手指抬起事件 134 * 重新计算样式 135 * @param gesturechange 136 */ 137 end: function (gesturechange) { 138 var _this = this; 139 var scale = gesturechange.scale; 140 setTimeout(function () { 141 console.log(gesturechange) 142 $("#showProportion").hide(); 143 },300) 144 } 145 }
1 //分辨率 2 function resize_resolution(proportion) { 3 width = document.body.clientWidth || document.documentElement.clientWidth; 4 height = document.body.clientHeight || document.documentElement.clientHeight; 5 w = width / 1863; 6 h = height / 855; 7 console.log(‘2‘, width, w, height, h); 8 w = parseFloat(w.toFixed(2)); 9 h = parseFloat(h.toFixed(2)); 10 if(proportion && proportion !== 1){ 11 w = w*proportion; 12 h = h*proportion; 13 } 14 15 16 //if(w != 1){ 17 $(‘.card‘).css({ 18 width: 305 * w + ‘px‘, 19 height: 160 * h + ‘px‘, 20 }); 21 $(‘.card .top‘).css({ 22 height: 80 * h + ‘px‘, 23 }); 24 $(‘.card .bed_no‘).css({ 25 width: 76.7 * w + ‘px‘, 26 lineHeight: 68 * h + ‘px‘, 27 fontSize: 46.97 * w + ‘px‘, 28 }); 29 $(‘.card .top .name_sex_age‘).css({ 30 // width: 227.3 * w + ‘px‘, 31 width: 220 * w + ‘px‘, 32 // height: 44 * h + ‘px‘, 33 height: 60 * h + ‘px‘, 34 // lineHeight: 44 * h + ‘px‘, 35 lineHeight: 60 * h + ‘px‘, 36 fontSize: 15.45 * w + ‘px‘, 37 }); 38 $(‘.card .top .name‘).css({ 39 // width: 140 * w + ‘px‘, 40 width: 130 * w + ‘px‘, 41 paddingLeft: 15 * w + ‘px‘, 42 fontSize: 36.7 * w + ‘px‘, 43 height: 44 * h + ‘px‘, 44 lineHeight: 44 * h + ‘px‘, 45 }); 46 $(‘.card .top .name p‘).css({ 47 height: 44 * h + ‘px‘, 48 width: 145 * w + ‘px‘, 49 }); 50 // $(‘.simple_card .card .top .name‘).css({ 51 // height: 60 * h + ‘px‘, 52 // lineHeight: 60 * h + ‘px‘, 53 // }); 54 $(‘.card .top .sex‘).css({ 55 width: 20 * w + ‘px‘, 56 fontSize: 15.45 * w + ‘px‘, 57 }); 58 $(‘.card .top .age‘).css({ 59 width: 50 * w + ‘px‘, 60 fontSize: 15.45 * w + ‘px‘, 61 }); 62 $(‘.card .top .id p:first-child‘).css({ 63 fontSize: 15.45 * w + ‘px‘, 64 }); 65 $(‘.card .top .id‘).css({ 66 height: 24 * h + ‘px‘, 67 paddingLeft: 15 * w + ‘px‘, 68 width: 119 * w + ‘px‘, 69 }); 70 $(‘.card .top .ryrq‘).css({}); 71 $(‘.card .middle‘).css({ 72 height: 58 * h + ‘px‘, 73 // marginTop: 5 * h + ‘px‘, 74 // marginBottom: 5 * h + ‘px‘, 75 marginTop: 0 * h + ‘px‘, 76 marginBottom: 0 * h + ‘px‘, 77 }); 78 $(‘.card .bottom‘).css({ 79 height: 15 * h + ‘px‘, 80 paddingLeft: 22 * w + ‘px‘, 81 paddingRight: 22 * w + ‘px‘, 82 fontSize: 15.45 * w + ‘px‘, 83 }); 84 $(‘.card .middle .label4‘).css({ 85 paddingLeft: 22 * w + ‘px‘, 86 height: 44 * h + ‘px‘, 87 paddingRight: 22 * w + ‘px‘, 88 }); 89 $(‘.card .label4 li‘).css({ 90 width: 44 * w + ‘px‘, 91 height: 44 * h + ‘px‘, 92 lineHeight: 44 * h + ‘px‘, 93 fontSize: 23.49 * w + ‘px‘, 94 }); 95 $(‘.card .middle .label5-8‘).css({ 96 marginTop: 15 * h + ‘px‘, 97 height: 36 * h + ‘px‘, 98 marginBottom: 15 * h + ‘px‘, 99 }); 100 $(‘.card .label5-8 li‘).css({ 101 width: 36 * w + ‘px‘, 102 height: 36 * w + ‘px‘, 103 lineHeight: 36 * w + ‘px‘, 104 fontSize: 20 * w + ‘px‘, 105 marginLeft: 5 * w + ‘px‘, 106 marginRight: 5 * w + ‘px‘, 107 }); 108 $(‘.card .middle .label9-16‘).css({ 109 paddingTop: 2 * h + ‘px‘, 110 paddingLeft: 3 * w + ‘px‘, 111 paddingRight: 3 * w + ‘px‘, 112 paddingBottom: 2 * h + ‘px‘, 113 marginTop: 10 * h + ‘px‘, 114 }); 115 $(‘.card .label9-16 li‘).css({ 116 width: 30 * w + ‘px‘, 117 height: 30 * w + ‘px‘, 118 lineHeight: 30 * w + ‘px‘, 119 fontSize: 17 * w + ‘px‘, 120 marginTop: 3 * h + ‘px‘, 121 marginLeft: 7.5 * w + ‘px‘, 122 marginRight: 7.5 * w + ‘px‘, 123 marginBottom: 10 * h + ‘px‘, 124 }); 125 // console.log(top.card_type); 126 if (top.card_type) { 127 $(‘.simple_card‘).css({ 128 width: 305 * w + ‘px‘, 129 // height: 60 * h + ‘px‘, 130 height: 82 * h + ‘px‘, 131 // flexBasis: 300 * w + ‘px‘, 132 flexBasis: 305 * w + ‘px‘, 133 // marginBottom: 22 * h + ‘px‘, 134 marginBottom: 10.2 * h + ‘px‘, 135 marignLeft: 5.2 * w + ‘px‘, 136 marginRiht: 5.2 * w + ‘px‘, 137 }); 138 // } 139 $(‘.simple_card .top‘).css({ 140 height: 82 * h + ‘px‘, 141 }); 142 $(‘.simple_card .top .bed_no‘).css({ 143 // minWidth: 60 * w + ‘px‘, 144 // height: 60 * h + ‘px‘, 145 // lineHeight: 60 * h + ‘px‘, 146 minWidth: 82 * w + ‘px‘, 147 height: 82 * h + ‘px‘, 148 lineHeight: 82 * h + ‘px‘, 149 }); 150 $(‘.simple_card .top .name_sex_age‘).css({ 151 height: 50 * h + ‘px‘, 152 lineHeight: 50 * h + ‘px‘, 153 }); 154 $(‘.simple_card .top .name‘).css({ 155 width: 130 * w + ‘px‘, 156 fontSize: 36 * w + ‘px‘, 157 lineHeight: 60 * h + ‘px‘, 158 paddingLeft: 5 + ‘px‘, 159 }); 160 $(‘.simple_card .card .top .ryrq‘).css({ 161 height: 30 * h + ‘px‘, 162 lineHeight: 30 * h + ‘px‘, 163 paddingLeft: 10 * w + ‘px‘, 164 width: 200 * w + ‘px‘, 165 }); 166 $(‘.card .top .name p‘).css({ 167 height: 44 * h + ‘px‘, 168 width: 100 * w + ‘px‘, 169 }); 170 $(‘.simple_card .top .sex‘).css({ 171 lineHeight: 65 * h + ‘px‘, 172 fontSize: 18 + w + ‘px‘, 173 }); 174 $(‘.simple_card .top .payOff‘).css({ 175 fontSize: 20 * w + ‘px‘, 176 // right: 16 * w + ‘px‘, 177 right: 5 * w + ‘px‘, 178 height: 60 * w + ‘px‘, 179 lineHeight: 65 * h + ‘px‘, 180 }); 181 } 182 //详情 183 $(‘.detail_container .top‘).css({ 184 height: 168 * h + ‘px‘, 185 property2: ‘value2‘ 186 }); 187 $(‘.detail_container .top .nav‘).css({ 188 height: 86 * h + ‘px‘, 189 lineHeight: 86 * h + ‘px‘, 190 fontSize: 40 * w + ‘px‘ 191 }); 192 $(‘.detail_container .top .simple_info‘).css({ 193 height: 82 * h + ‘px‘, 194 fontSize: 32 * w + ‘px‘ 195 }); 196 $(‘.detail_container .simple_info .left‘).css({ 197 width: 1000 * w + ‘px‘ 198 }); 199 $(‘.detail_container .simple_info .left ul‘).css({ 200 height: 82 * h + ‘px‘, 201 lineHeight: 82 * w + ‘px‘ 202 }); 203 $(‘.detail_container .simple_info .left li‘).css({ 204 marginLeft: 45 * w + ‘px‘ 205 }); 206 $(‘.detail_container .simple_info .right‘).css({ 207 width: 800 * w + ‘px‘ 208 }); 209 $(‘.detail_container .simple_info .right ul‘).css({ 210 marginTop: 19 * h + ‘px‘, 211 marginLeft: 19 * w + ‘px‘, 212 marginRight: 19 * w + ‘px‘, 213 marginBottom: 19 * h + ‘px‘ 214 }); 215 $(‘.detail_container .middle‘).css({ 216 height: 640 * h + ‘px‘, 217 fontSize: 30 * w + ‘px‘ 218 }); 219 $(‘.detail_container .middle table‘).css({ 220 width: 1730 * w + ‘px‘, 221 fontSize: 30 * w + ‘px‘ 222 }); 223 $(‘.detail_container .middle td.left‘).css({ 224 width: 250 * w + ‘px‘ 225 }); 226 $(‘.detail_container .middle td.right‘).css({ 227 width: 610 * w + ‘px‘ 228 }); 229 $(‘.detail_container .middle td‘).css({ 230 lineHeight: 81 * h + ‘px‘ 231 }); 232 $(‘.detail_container .bottom‘).css({ 233 height: 59 * h + ‘px‘, 234 lineHeight: 59 * h + ‘px‘ 235 }); 236 //} 237 }
页面效果:
方式4:
将iframe中的页面单独拿出来,有安卓加一个新的webview,加载这个页面,手势动作由安卓端显示,手指拿开后,传一个缩放比例来,前端计算需要显示的页面样式。
标签:ext 现在 amp padding exp wing oop custom inter
原文地址:https://www.cnblogs.com/pangchunlei/p/14231013.html