1、单页面,head标签里增加属性:
/*注释*/
// width:控制 viewport 的大小,可以指定的一个值,如果 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)
// height:和 width 相对应,指定高度。
// initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
// maximum-scale:允许用户缩放到的最大比例。
// minimum-scale:允许用户缩放到的最小比例。
// user-scalable:用户是否可以手动缩放
//
2、iframe引入的页面,想要缩放,这个标签就不生效了
![](https://img2020.cnblogs.com/blog/932676/202101/932676-20210113092521582-1223384428.png)
手势动作,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);
![](https://img2020.cnblogs.com/blog/932676/202101/932676-20210104171130812-514451082.gif)
动作:放-缩-放,缩放比例数值(scale)会根据手势动作变化
(2)根据手指动作,判断缩放,并体现在页面上:
方式1:
缩放事件响应时候,调节页面大小。初步实现方式是,缩放事件响应后,根据两指距离,调节scale属性,放大或缩小页面。
![](https://img2020.cnblogs.com/blog/932676/202101/932676-20210104171247206-1364678031.gif)
代码(错误示范):
//缩放比例
var scalingRatio = 1;
var DOM = $("#image");
function touchChange(n,gesturechange) {
if(gesturechange.scale>1){
// 放大
if(scalingRatio0.5)scalingRatio -= gesturechange.scale
}
DOM.css({'transform':'scale('+scalingRatio+')'})
n+= ""+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:
Pinchzoom.js Demo
div.pinch-zoom,
div.pinch-zoom img{
width: 100%;
-webkit-user-drag: none;
}
$(function () {
$('div.pinch-zoom').each(function () {
new RTP.PinchZoom($(this), {});
});
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
JS:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
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 = $('');
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+""+
546 "|||zoomFactor:"+zoomFactor+""+
547 "|||transform2d:"+transform2d+""+
548 "|||transform3d:"+transform3d+""+
549 "|||this.lastScale:"+this.lastScale+""+
550 "|||this.lastZoomCenter:"+JSON.stringify(this.lastZoomCenter)+""+
551 "|||this.offset:"+JSON.stringify(this.offset)+""+
552 "|||this.nthZoom:"+this.nthZoom+""+
553 "|||this.zoomFactor:"+this.zoomFactor+""+
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
页面效果:
![](https://img2020.cnblogs.com/blog/932676/202101/932676-20210104171745967-173133627.gif)
方式3:
[1] 同样的随手势变化,实时监听中心坐标、两指见的距离,显示改变的百分比
[2] 手指抬起后,获取最终变化的百分比,计算页面样式重绘
该方案存在问题:
安卓分机上,获取手指离开事件,有几率获取不到(加一个1s的延时,直接关闭提示,并计算样式)。
重新计算样式,触发浏览器重绘动作,数据量较大的情况下,时间会比较慢。
完整代码:
HTML:
100%
JS:
(function(){
var isTouch = false;
var isDoubleTouch = false; //是否为多触点
var start = []; //存放触点坐标
var now, delta; //当前时间,两次触发事件时间差
var timer = null; //计时器,触发单击事件
var startPosition, movePosition, endPosition; //滑动起点,移动,结束点坐标
//事件声明
var gesturestart = new CustomEvent('gesturestart');
var gesturechange = new CustomEvent('gesturechange');
var gestureend = new CustomEvent('gestureend');
var swipeMove = new CustomEvent('swipeMove');
var doubleTouch = new CustomEvent("doubleTouch");
var oneTouch = new CustomEvent("oneTouch");
//监听touchstart事件
document.addEventListener('touchstart', function(e) {
if (e.touches.length >= 2) { //判断是否有两个点在屏幕上
isDoubleTouch = true;
start = e.touches; //得到第一组两个点
var screenMinPoint = getMidpoint(start[0], start[1]); //获取两个触点中心坐标
gesturestart.midPoint = [screenMinPoint[0] - e.target.offsetLeft, screenMinPoint[1] - e.target.offsetTop]; //获取中心点坐标相对目标元素坐标
e.target.dispatchEvent(gesturestart);
} else {
delta = Date.now() - now; //计算两次点击时间差
now = Date.now();
startPosition = [e.touches[0].pageX, e.touches[0].pageY];
if (delta > 0 && delta = 2 && isDoubleTouch) { //手势事件
var now = e.touches; //得到第二组两个点
// console.log('移动:',now);
var scale = getDistance(now[0], now[1]) / getDistance(start[0], start[1]); //得到缩放比例
var rotation = getAngle(now[0], now[1]) - getAngle(start[0], start[1]); //得到旋转角度差
gesturechange.scale = scale.toFixed(2);
gesturechange.rotation = rotation.toFixed(2);
e.target.dispatchEvent(gesturechange);
if(Math.abs(now[1]['clientX'] - now[0]['clientX'])>50 && Math.abs(now[1]['clientY'] - now[0]['clientY']) 200){
scale = 200;
}else if (scale < 70){
scale = 70;
}
scale += '%';
setTimeout(function () {
_this.closeProportion()
},1000)
$("#showProportion").show();
$("#showProportion").html(scale);
},
/**
* 如果比例提示框还在显示,
* 关闭提示框,并计算样式
*/
closeProportion:function(){
var _this = this;
if($('#showProportion').is(':hidden')){
//如果隐藏时。。。
}else{
//如果显示时。。。
_this.end({scale: _this.scalingRatio})
}
},
/**
* 手指抬起事件
* 重新计算样式
* @param gesturechange
*/
end: function (gesturechange) {
var _this = this;
var scale = gesturechange.scale;
if(scale > 2){
scale = 2;
}else if (scale < 0.7){
scale = 0.7;
}
_this.scalingRatio = scale;
$("#showProportion").hide();
resize_resolution(scale);
}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
View Code
页面效果:
![](https://img2020.cnblogs.com/blog/932676/202101/932676-20210104172010305-1358531451.gif)
方式4:
将iframe中的页面单独拿出来,有安卓加一个新的webview,加载这个页面,手势动作由安卓端显示,手指拿开后,传一个缩放比例来,前端计算需要显示的页面样式。
|