504 lines
15 KiB
JavaScript
504 lines
15 KiB
JavaScript
(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 = $('<div class="pinch-zoom-container"></div>');
|
|
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); |