(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 - .01 && value < expected + .01 }; PinchZoom.prototype = { defaults: { tapZoomFactor: 2, zoomOutFactor: 1.3, animationDuration: 300, animationInterval: 5, maxZoom: 4, minZoom: .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) { 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, this.options.animationInterval, 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, this.options.animationInterval, 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, this.options.animationInterval, 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, interval, 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(); setTimeout(renderFrame, interval) } }.bind(this); this.inAnimation = true; renderFrame() }, stopAnimation: function() { this.inAnimation = false }, swing: function(p) { return -Math.cos(p * Math.PI) / 2 + .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 = $('
'); this.el.before(this.container); this.container.append(this.el); this.container.css({ overflow: "hidden", //position: "relative" position:"absolute", top:"0", }); 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(); let Scwidth = this.el[0].clientWidth/this.el.find('img')[0].naturalWidth; let ScHeight = this.el[0].clientHeight / this.el.find('img')[0].naturalHeight; 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 } this.el.find('.paperMap').css('opacity','1'); this.el.parents('.imgBoxCont').find('.pichmask').css('opacity','0'); }.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);