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

5 years ago
(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);