1.哪里找?
https://github.com/jias/pinchzoom
https://www.jq22.com/jquery-info9795
2.有什么作用?
提供了多点触摸手势,可以对任何 DOM 元素进行缩放和拖动 ,例如: 双击图片放大 , 双指放大缩小等
3.如何做?
示例:
html
<div class="page"> <div class="pinch-zoom-container" style="overflow: hidden; position: relative; height: 333px;"> <div class="pinch-zoom" style="transform-origin: 0% 0%; position: absolute; transform: scale(1, 1) translate(0px, 0px);"> <img src="https://img.hbhcdn.com/zhuanti/20919/pazzle02.jpg" alt=""> </div> </div> </div>
css 1rem = 50px
body, html { text-align:center; color:#fff } body { background-color:#333; font-family:Georgia } .page{ width: 6.66rem; height:6.66rem; margin:5rem auto; } .pinch-zoom-container{ overflow: inherit !important; } .pinch-zoom, .pinch-zoom img{ width: 6.66rem; height:6.66rem; -webkit-user-drag: none; -moz-user-drag: none; -ms-user-drag: none; user-drag: none; }
js
$('div.pinch-zoom').each(function () { new RTP.PinchZoom($(this), {}); });
由源码可知 相关配置参数
new RTP.PinchZoom($(this), { tapZoomFactor: // 双击可缩放到的缩放因子。(默认值2) zoomOutFactor: // 当缩放因子低于配置值时,调整为原始大小。(默认值1.3) animationDuration: // 动画持续时间(毫秒)。(默认300) maxZoom: // 最大缩放因子。(默认值4) minZoom: // 最小缩放因子。(默认值为0.5) });
源码
(function () { 'use strict'; var definePinchZoom = function ($) { var PinchZoom = function (el, options) { this.el = $(el); this.zoomFactor = 1; this.lastScale = 1; this.offset = { x: 0, y: 0 }; this.options = $.extend({}, this.defaults, options); this.setupMarkup(); this.bindEvents(); this.update(); this.enable(); }, sum = function (a, b) { return a + b; }, isCloseTo = function (value, expected) { return value > expected - 0.01 && value < expected + 0.01; }; PinchZoom.prototype = { defaults: { tapZoomFactor: 2, zoomOutFactor: 1.3, animationDuration: 300, maxZoom: 4, minZoom: 0.5, lockDragAxis: false, use2d: true, zoomStartEventName: 'pz_zoomstart', zoomEndEventName: 'pz_zoomend', dragStartEventName: 'pz_dragstart', dragEndEventName: 'pz_dragend', doubleTapEventName: 'pz_doubletap' }, handleDragStart: function (event) { this.el.trigger(this.options.dragStartEventName); this.stopAnimation(); this.lastDragPosition = false; this.hasInteraction = true; this.handleDrag(event); }, handleDrag: function (event) { if (this.zoomFactor > 1.0) { var touch = this.getTouches(event)[0]; this.drag(touch, this.lastDragPosition); this.offset = this.sanitizeOffset(this.offset); this.lastDragPosition = touch; } }, handleDragEnd: function () { this.el.trigger(this.options.dragEndEventName); this.end(); }, handleZoomStart: function (event) { this.el.trigger(this.options.zoomStartEventName); this.stopAnimation(); this.lastScale = 1; this.nthZoom = 0; this.lastZoomCenter = false; this.hasInteraction = true; }, handleZoom: function (event, newScale) { var touchCenter = this.getTouchCenter(this.getTouches(event)), scale = newScale / this.lastScale; this.lastScale = newScale; this.nthZoom += 1; if (this.nthZoom > 3) { this.scale(scale, touchCenter); this.drag(touchCenter, this.lastZoomCenter); } this.lastZoomCenter = touchCenter; }, handleZoomEnd: function () { this.el.trigger(this.options.zoomEndEventName); this.end(); }, handleDoubleTap: function (event) { var center = this.getTouches(event)[0], zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor, startZoomFactor = this.zoomFactor, updateProgress = (function (progress) { this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); }).bind(this); if (this.hasInteraction) { return; } if (startZoomFactor > zoomFactor) { center = this.getCurrentZoomCenter(); } this.animate(this.options.animationDuration, updateProgress, this.swing); this.el.trigger(this.options.doubleTapEventName); }, sanitizeOffset: function (offset) { var maxX = (this.zoomFactor - 1) * this.getContainerX(), maxY = (this.zoomFactor - 1) * this.getContainerY(), maxOffsetX = Math.max(maxX, 0), maxOffsetY = Math.max(maxY, 0), minOffsetX = Math.min(maxX, 0), minOffsetY = Math.min(maxY, 0); return { x: Math.min(Math.max(offset.x, minOffsetX), maxOffsetX), y: Math.min(Math.max(offset.y, minOffsetY), maxOffsetY) }; }, scaleTo: function (zoomFactor, center) { this.scale(zoomFactor / this.zoomFactor, center); }, scale: function (scale, center) { scale = this.scaleZoomFactor(scale); this.addOffset({ x: (scale - 1) * (center.x + this.offset.x), y: (scale - 1) * (center.y + this.offset.y) }); }, scaleZoomFactor: function (scale) { var originalZoomFactor = this.zoomFactor; this.zoomFactor *= scale; this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options .minZoom)); return this.zoomFactor / originalZoomFactor; }, drag: function (center, lastCenter) { if (lastCenter) { if (this.options.lockDragAxis) { if (Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) { this.addOffset({ x: -(center.x - lastCenter.x), y: 0 }); } else { this.addOffset({ y: -(center.y - lastCenter.y), x: 0 }); } } else { this.addOffset({ y: -(center.y - lastCenter.y), x: -(center.x - lastCenter.x) }); } } }, getTouchCenter: function (touches) { return this.getVectorAvg(touches); }, getVectorAvg: function (vectors) { return { x: vectors.map(function (v) { return v.x; }).reduce(sum) / vectors.length, y: vectors.map(function (v) { return v.y; }).reduce(sum) / vectors.length }; }, addOffset: function (offset) { this.offset = { x: this.offset.x + offset.x, y: this.offset.y + offset.y }; }, sanitize: function () { if (this.zoomFactor < this.options.zoomOutFactor) { this.zoomOutAnimation(); } else if (this.isInsaneOffset(this.offset)) { this.sanitizeOffsetAnimation(); } }, isInsaneOffset: function (offset) { var sanitizedOffset = this.sanitizeOffset(offset); return sanitizedOffset.x !== offset.x || sanitizedOffset.y !== offset.y; }, sanitizeOffsetAnimation: function () { var targetOffset = this.sanitizeOffset(this.offset), startOffset = { x: this.offset.x, y: this.offset.y }, updateProgress = (function (progress) { this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x); this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y); this.update(); }).bind(this); this.animate(this.options.animationDuration, updateProgress, this.swing); }, zoomOutAnimation: function () { var startZoomFactor = this.zoomFactor, zoomFactor = 1, center = this.getCurrentZoomCenter(), updateProgress = (function (progress) { this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); }).bind(this); this.animate(this.options.animationDuration, updateProgress, this.swing); }, updateAspectRatio: function () { this.setContainerY(this.getContainerX() / this.getAspectRatio()); }, getInitialZoomFactor: function () { return this.container[0].offsetWidth / this.el[0].offsetWidth; }, getAspectRatio: function () { return this.el[0].offsetWidth / this.el[0].offsetHeight; }, getCurrentZoomCenter: function () { 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), 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); if (offsetRight === 0) { centerX = this.container[0].offsetWidth; } if (offsetBottom === 0) { centerY = this.container[0].offsetHeight; } return { x: centerX, y: centerY }; }, canDrag: function () { return !isCloseTo(this.zoomFactor, 1); }, getTouches: function (event) { var position = this.container.offset(); return Array.prototype.slice.call(event.touches).map(function (touch) { return { x: touch.pageX - position.left, y: touch.pageY - position.top }; }); }, animate: function (duration, framefn, timefn, callback) { var startTime = new Date().getTime(), renderFrame = (function () { if (!this.inAnimation) { return; } var frameTime = new Date().getTime() - startTime, progress = frameTime / duration; if (frameTime >= duration) { framefn(1); if (callback) { callback(); } this.update(); this.stopAnimation(); this.update(); } else { if (timefn) { progress = timefn(progress); } framefn(progress); this.update(); requestAnimationFrame(renderFrame); } }).bind(this); this.inAnimation = true; requestAnimationFrame(renderFrame); }, stopAnimation: function () { this.inAnimation = false; }, swing: function (p) { return -Math.cos(p * Math.PI) / 2 + 0.5; }, getContainerX: function () { return this.container[0].offsetWidth; }, getContainerY: function () { return this.container[0].offsetHeight; }, setContainerY: function (y) { return this.container.height(y); }, setupMarkup: function () { this.container = $('<div class="pinch-zoom-container"></div>'); this.el.before(this.container); this.container.append(this.el); this.container.css({ 'overflow': 'hidden', 'position': 'relative' }); this.el.css({ '-webkit-transform-origin': '0% 0%', '-moz-transform-origin': '0% 0%', '-ms-transform-origin': '0% 0%', '-o-transform-origin': '0% 0%', 'transform-origin': '0% 0%', 'position': 'absolute' }); }, end: function () { this.hasInteraction = false; this.sanitize(); this.update(); }, bindEvents: function () { detectGestures(this.container.get(0), this); $(window).on('resize', this.update.bind(this)); $(this.el).find('img').on('load', this.update.bind(this)); }, 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); 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 { 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); }, enable: function () { this.enabled = true; }, disable: function () { this.enabled = false; } }; var detectGestures = function (el, target) { var interaction = null, fingers = 0, lastTouchStart = null, startTouches = null, setInteraction = function (newInteraction, event) { if (interaction !== newInteraction) { if (interaction && !newInteraction) { switch (interaction) { case "zoom": target.handleZoomEnd(event); break; case 'drag': target.handleDragEnd(event); break; } } switch (newInteraction) { case 'zoom': target.handleZoomStart(event); break; case 'drag': target.handleDragStart(event); break; } } interaction = newInteraction; }, updateInteraction = function (event) { if (fingers === 2) { setInteraction('zoom'); } else if (fingers === 1 && target.canDrag()) { setInteraction('drag', event); } else { setInteraction(null, event); } }, targetTouches = function (touches) { return Array.prototype.slice.call(touches).map(function (touch) { return { x: touch.pageX, y: touch.pageY }; }); }, getDistance = function (a, b) { var x, y; x = a.x - b.x; y = a.y - b.y; return Math.sqrt(x * x + y * y); }, calculateScale = function (startTouches, endTouches) { var startDistance = getDistance(startTouches[0], startTouches[1]), endDistance = getDistance(endTouches[0], endTouches[1]); return endDistance / startDistance; }, cancelEvent = function (event) { event.stopPropagation(); event.preventDefault(); }, detectDoubleTap = function (event) { var time = (new Date()).getTime(); if (fingers > 1) { lastTouchStart = null; } if (time - lastTouchStart < 300) { cancelEvent(event); target.handleDoubleTap(event); switch (interaction) { case "zoom": target.handleZoomEnd(event); break; case 'drag': target.handleDragEnd(event); break; } } if (fingers === 1) { lastTouchStart = time; } }, firstMove = true; el.addEventListener('touchstart', function (event) { if (target.enabled) { firstMove = true; fingers = event.touches.length; detectDoubleTap(event); } }); el.addEventListener('touchmove', function (event) { if (target.enabled) { if (firstMove) { updateInteraction(event); if (interaction) { cancelEvent(event); } startTouches = targetTouches(event.touches); } else { 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; } }); el.addEventListener('touchend', function (event) { if (target.enabled) { fingers = event.touches.length; updateInteraction(event); } }); }; return PinchZoom; }; if (typeof define !== 'undefined' && define.amd) { define(['jquery'], function ($) { return definePinchZoom($); }); } else { window.RTP = window.RTP || {}; window.RTP.PinchZoom = definePinchZoom(window.$); } }).call(this);
....