You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
12 KiB
322 lines
12 KiB
(function($, window) { |
|
var CLASS_ZOOM = $.className('zoom'); |
|
var CLASS_ZOOM_SCROLLER = $.className('zoom-scroller'); |
|
|
|
var SELECTOR_ZOOM = '.' + CLASS_ZOOM; |
|
var SELECTOR_ZOOM_SCROLLER = '.' + CLASS_ZOOM_SCROLLER; |
|
|
|
var EVENT_PINCH_START = 'pinchstart'; |
|
var EVENT_PINCH = 'pinch'; |
|
var EVENT_PINCH_END = 'pinchend'; |
|
if ('ongesturestart' in window) { |
|
EVENT_PINCH_START = 'gesturestart'; |
|
EVENT_PINCH = 'gesturechange'; |
|
EVENT_PINCH_END = 'gestureend'; |
|
} |
|
$.Zoom = function(element, options) { |
|
var zoom = this; |
|
|
|
zoom.options = $.extend($.Zoom.defaults, options); |
|
|
|
zoom.wrapper = zoom.element = element; |
|
zoom.scroller = element.querySelector(SELECTOR_ZOOM_SCROLLER); |
|
zoom.scrollerStyle = zoom.scroller && zoom.scroller.style; |
|
|
|
zoom.zoomer = element.querySelector(SELECTOR_ZOOM); |
|
zoom.zoomerStyle = zoom.zoomer && zoom.zoomer.style; |
|
|
|
zoom.init = function() { |
|
//自动启用 |
|
$.options.gestureConfig.pinch = true; |
|
$.options.gestureConfig.doubletap = true; |
|
zoom.initEvents(); |
|
}; |
|
|
|
zoom.initEvents = function(detach) { |
|
var action = detach ? 'removeEventListener' : 'addEventListener'; |
|
var target = zoom.scroller; |
|
|
|
target[action](EVENT_PINCH_START, zoom.onPinchstart); |
|
target[action](EVENT_PINCH, zoom.onPinch); |
|
target[action](EVENT_PINCH_END, zoom.onPinchend); |
|
|
|
target[action]($.EVENT_START, zoom.onTouchstart); |
|
target[action]($.EVENT_MOVE, zoom.onTouchMove); |
|
target[action]($.EVENT_CANCEL, zoom.onTouchEnd); |
|
target[action]($.EVENT_END, zoom.onTouchEnd); |
|
|
|
target[action]('drag', zoom.dragEvent); |
|
target[action]('doubletap', zoom.doubleTapEvent); |
|
}; |
|
zoom.dragEvent = function(e) { |
|
if (imageIsMoved || isGesturing) { |
|
e.stopPropagation(); |
|
} |
|
}; |
|
zoom.doubleTapEvent = function(e) { |
|
zoom.toggleZoom(e.detail.center); |
|
}; |
|
zoom.transition = function(style, time) { |
|
time = time || 0; |
|
style['webkitTransitionDuration'] = time + 'ms'; |
|
return zoom; |
|
}; |
|
zoom.translate = function(style, x, y) { |
|
x = x || 0; |
|
y = y || 0; |
|
style['webkitTransform'] = 'translate3d(' + x + 'px,' + y + 'px,0px)'; |
|
return zoom; |
|
}; |
|
zoom.scale = function(style, scale) { |
|
scale = scale || 1; |
|
style['webkitTransform'] = 'translate3d(0,0,0) scale(' + scale + ')'; |
|
return zoom; |
|
}; |
|
zoom.scrollerTransition = function(time) { |
|
return zoom.transition(zoom.scrollerStyle, time); |
|
}; |
|
zoom.scrollerTransform = function(x, y) { |
|
return zoom.translate(zoom.scrollerStyle, x, y); |
|
}; |
|
zoom.zoomerTransition = function(time) { |
|
return zoom.transition(zoom.zoomerStyle, time); |
|
}; |
|
zoom.zoomerTransform = function(scale) { |
|
return zoom.scale(zoom.zoomerStyle, scale); |
|
}; |
|
|
|
// Gestures |
|
var scale = 1, |
|
currentScale = 1, |
|
isScaling = false, |
|
isGesturing = false; |
|
zoom.onPinchstart = function(e) { |
|
isGesturing = true; |
|
}; |
|
zoom.onPinch = function(e) { |
|
if (!isScaling) { |
|
zoom.zoomerTransition(0); |
|
isScaling = true; |
|
} |
|
scale = (e.detail ? e.detail.scale : e.scale) * currentScale; |
|
if (scale > zoom.options.maxZoom) { |
|
scale = zoom.options.maxZoom - 1 + Math.pow((scale - zoom.options.maxZoom + 1), 0.5); |
|
} |
|
if (scale < zoom.options.minZoom) { |
|
scale = zoom.options.minZoom + 1 - Math.pow((zoom.options.minZoom - scale + 1), 0.5); |
|
} |
|
zoom.zoomerTransform(scale); |
|
}; |
|
zoom.onPinchend = function(e) { |
|
scale = Math.max(Math.min(scale, zoom.options.maxZoom), zoom.options.minZoom); |
|
zoom.zoomerTransition(zoom.options.speed).zoomerTransform(scale); |
|
currentScale = scale; |
|
isScaling = false; |
|
}; |
|
zoom.setZoom = function(newScale) { |
|
scale = currentScale = newScale; |
|
zoom.scrollerTransition(zoom.options.speed).scrollerTransform(0, 0); |
|
zoom.zoomerTransition(zoom.options.speed).zoomerTransform(scale); |
|
}; |
|
zoom.toggleZoom = function(position, speed) { |
|
if (typeof position === 'number') { |
|
speed = position; |
|
position = undefined; |
|
} |
|
speed = typeof speed === 'undefined' ? zoom.options.speed : speed; |
|
if (scale && scale !== 1) { |
|
scale = currentScale = 1; |
|
zoom.scrollerTransition(speed).scrollerTransform(0, 0); |
|
} else { |
|
scale = currentScale = zoom.options.maxZoom; |
|
if (position) { |
|
var offset = $.offset(zoom.zoomer); |
|
var top = offset.top; |
|
var left = offset.left; |
|
var offsetX = (position.x - left) * scale; |
|
var offsetY = (position.y - top) * scale; |
|
this._cal(); |
|
if (offsetX >= imageMaxX && offsetX <= (imageMaxX + wrapperWidth)) { //center |
|
offsetX = imageMaxX - offsetX + wrapperWidth / 2; |
|
} else if (offsetX < imageMaxX) { //left |
|
offsetX = imageMaxX - offsetX + wrapperWidth / 2; |
|
} else if (offsetX > (imageMaxX + wrapperWidth)) { //right |
|
offsetX = imageMaxX + wrapperWidth - offsetX - wrapperWidth / 2; |
|
} |
|
if (offsetY >= imageMaxY && offsetY <= (imageMaxY + wrapperHeight)) { //middle |
|
offsetY = imageMaxY - offsetY + wrapperHeight / 2; |
|
} else if (offsetY < imageMaxY) { //top |
|
offsetY = imageMaxY - offsetY + wrapperHeight / 2; |
|
} else if (offsetY > (imageMaxY + wrapperHeight)) { //bottom |
|
offsetY = imageMaxY + wrapperHeight - offsetY - wrapperHeight / 2; |
|
} |
|
offsetX = Math.min(Math.max(offsetX, imageMinX), imageMaxX); |
|
offsetY = Math.min(Math.max(offsetY, imageMinY), imageMaxY); |
|
zoom.scrollerTransition(speed).scrollerTransform(offsetX, offsetY); |
|
} else { |
|
zoom.scrollerTransition(speed).scrollerTransform(0, 0); |
|
} |
|
} |
|
zoom.zoomerTransition(speed).zoomerTransform(scale); |
|
}; |
|
|
|
zoom._cal = function() { |
|
wrapperWidth = zoom.wrapper.offsetWidth; |
|
wrapperHeight = zoom.wrapper.offsetHeight; |
|
imageWidth = zoom.zoomer.offsetWidth; |
|
imageHeight = zoom.zoomer.offsetHeight; |
|
var scaledWidth = imageWidth * scale; |
|
var scaledHeight = imageHeight * scale; |
|
imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0); |
|
imageMaxX = -imageMinX; |
|
imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0); |
|
imageMaxY = -imageMinY; |
|
}; |
|
|
|
var wrapperWidth, wrapperHeight, imageIsTouched, imageIsMoved, imageCurrentX, imageCurrentY, imageMinX, imageMinY, imageMaxX, imageMaxY, imageWidth, imageHeight, imageTouchesStart = {}, |
|
imageTouchesCurrent = {}, |
|
imageStartX, imageStartY, velocityPrevPositionX, velocityPrevTime, velocityX, velocityPrevPositionY, velocityY; |
|
|
|
zoom.onTouchstart = function(e) { |
|
e.preventDefault(); |
|
imageIsTouched = true; |
|
imageTouchesStart.x = e.type === $.EVENT_START ? e.targetTouches[0].pageX : e.pageX; |
|
imageTouchesStart.y = e.type === $.EVENT_START ? e.targetTouches[0].pageY : e.pageY; |
|
}; |
|
zoom.onTouchMove = function(e) { |
|
e.preventDefault(); |
|
if (!imageIsTouched) return; |
|
if (!imageIsMoved) { |
|
wrapperWidth = zoom.wrapper.offsetWidth; |
|
wrapperHeight = zoom.wrapper.offsetHeight; |
|
imageWidth = zoom.zoomer.offsetWidth; |
|
imageHeight = zoom.zoomer.offsetHeight; |
|
var translate = $.parseTranslateMatrix($.getStyles(zoom.scroller, 'webkitTransform')); |
|
imageStartX = translate.x || 0; |
|
imageStartY = translate.y || 0; |
|
zoom.scrollerTransition(0); |
|
} |
|
var scaledWidth = imageWidth * scale; |
|
var scaledHeight = imageHeight * scale; |
|
|
|
if (scaledWidth < wrapperWidth && scaledHeight < wrapperHeight) return; |
|
|
|
imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0); |
|
imageMaxX = -imageMinX; |
|
imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0); |
|
imageMaxY = -imageMinY; |
|
|
|
imageTouchesCurrent.x = e.type === $.EVENT_MOVE ? e.targetTouches[0].pageX : e.pageX; |
|
imageTouchesCurrent.y = e.type === $.EVENT_MOVE ? e.targetTouches[0].pageY : e.pageY; |
|
|
|
if (!imageIsMoved && !isScaling) { |
|
// if (Math.abs(imageTouchesCurrent.y - imageTouchesStart.y) < Math.abs(imageTouchesCurrent.x - imageTouchesStart.x)) { |
|
//TODO 此处需要优化,当遇到长图,需要上下滚动时,下列判断会导致滚动不流畅 |
|
if ( |
|
(Math.floor(imageMinX) === Math.floor(imageStartX) && imageTouchesCurrent.x < imageTouchesStart.x) || |
|
(Math.floor(imageMaxX) === Math.floor(imageStartX) && imageTouchesCurrent.x > imageTouchesStart.x) |
|
) { |
|
imageIsTouched = false; |
|
return; |
|
} |
|
// } |
|
} |
|
imageIsMoved = true; |
|
imageCurrentX = imageTouchesCurrent.x - imageTouchesStart.x + imageStartX; |
|
imageCurrentY = imageTouchesCurrent.y - imageTouchesStart.y + imageStartY; |
|
|
|
if (imageCurrentX < imageMinX) { |
|
imageCurrentX = imageMinX + 1 - Math.pow((imageMinX - imageCurrentX + 1), 0.8); |
|
} |
|
if (imageCurrentX > imageMaxX) { |
|
imageCurrentX = imageMaxX - 1 + Math.pow((imageCurrentX - imageMaxX + 1), 0.8); |
|
} |
|
|
|
if (imageCurrentY < imageMinY) { |
|
imageCurrentY = imageMinY + 1 - Math.pow((imageMinY - imageCurrentY + 1), 0.8); |
|
} |
|
if (imageCurrentY > imageMaxY) { |
|
imageCurrentY = imageMaxY - 1 + Math.pow((imageCurrentY - imageMaxY + 1), 0.8); |
|
} |
|
|
|
//Velocity |
|
if (!velocityPrevPositionX) velocityPrevPositionX = imageTouchesCurrent.x; |
|
if (!velocityPrevPositionY) velocityPrevPositionY = imageTouchesCurrent.y; |
|
if (!velocityPrevTime) velocityPrevTime = $.now(); |
|
velocityX = (imageTouchesCurrent.x - velocityPrevPositionX) / ($.now() - velocityPrevTime) / 2; |
|
velocityY = (imageTouchesCurrent.y - velocityPrevPositionY) / ($.now() - velocityPrevTime) / 2; |
|
if (Math.abs(imageTouchesCurrent.x - velocityPrevPositionX) < 2) velocityX = 0; |
|
if (Math.abs(imageTouchesCurrent.y - velocityPrevPositionY) < 2) velocityY = 0; |
|
velocityPrevPositionX = imageTouchesCurrent.x; |
|
velocityPrevPositionY = imageTouchesCurrent.y; |
|
velocityPrevTime = $.now(); |
|
|
|
zoom.scrollerTransform(imageCurrentX, imageCurrentY); |
|
}; |
|
zoom.onTouchEnd = function(e) { |
|
if (!e.touches.length) { |
|
isGesturing = false; |
|
} |
|
if (!imageIsTouched || !imageIsMoved) { |
|
imageIsTouched = false; |
|
imageIsMoved = false; |
|
return; |
|
} |
|
imageIsTouched = false; |
|
imageIsMoved = false; |
|
var momentumDurationX = 300; |
|
var momentumDurationY = 300; |
|
var momentumDistanceX = velocityX * momentumDurationX; |
|
var newPositionX = imageCurrentX + momentumDistanceX; |
|
var momentumDistanceY = velocityY * momentumDurationY; |
|
var newPositionY = imageCurrentY + momentumDistanceY; |
|
|
|
if (velocityX !== 0) momentumDurationX = Math.abs((newPositionX - imageCurrentX) / velocityX); |
|
if (velocityY !== 0) momentumDurationY = Math.abs((newPositionY - imageCurrentY) / velocityY); |
|
var momentumDuration = Math.max(momentumDurationX, momentumDurationY); |
|
|
|
imageCurrentX = newPositionX; |
|
imageCurrentY = newPositionY; |
|
|
|
var scaledWidth = imageWidth * scale; |
|
var scaledHeight = imageHeight * scale; |
|
imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0); |
|
imageMaxX = -imageMinX; |
|
imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0); |
|
imageMaxY = -imageMinY; |
|
imageCurrentX = Math.max(Math.min(imageCurrentX, imageMaxX), imageMinX); |
|
imageCurrentY = Math.max(Math.min(imageCurrentY, imageMaxY), imageMinY); |
|
|
|
zoom.scrollerTransition(momentumDuration).scrollerTransform(imageCurrentX, imageCurrentY); |
|
}; |
|
zoom.destroy = function() { |
|
zoom.initEvents(true); //detach |
|
delete $.data[zoom.wrapper.getAttribute('data-zoomer')]; |
|
zoom.wrapper.setAttribute('data-zoomer', ''); |
|
} |
|
zoom.init(); |
|
return zoom; |
|
}; |
|
$.Zoom.defaults = { |
|
speed: 300, |
|
maxZoom: 3, |
|
minZoom: 1, |
|
}; |
|
$.fn.zoom = function(options) { |
|
var zoomApis = []; |
|
this.each(function() { |
|
var zoomApi = null; |
|
var self = this; |
|
var id = self.getAttribute('data-zoomer'); |
|
if (!id) { |
|
id = ++$.uuid; |
|
$.data[id] = zoomApi = new $.Zoom(self, options); |
|
self.setAttribute('data-zoomer', id); |
|
} else { |
|
zoomApi = $.data[id]; |
|
} |
|
zoomApis.push(zoomApi); |
|
}); |
|
return zoomApis.length === 1 ? zoomApis[0] : zoomApis; |
|
}; |
|
})(mui, window); |