(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);