mui.zoom.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. (function($, window) {
  2. var CLASS_ZOOM = $.className('zoom');
  3. var CLASS_ZOOM_SCROLLER = $.className('zoom-scroller');
  4. var SELECTOR_ZOOM = '.' + CLASS_ZOOM;
  5. var SELECTOR_ZOOM_SCROLLER = '.' + CLASS_ZOOM_SCROLLER;
  6. var EVENT_PINCH_START = 'pinchstart';
  7. var EVENT_PINCH = 'pinch';
  8. var EVENT_PINCH_END = 'pinchend';
  9. if ('ongesturestart' in window) {
  10. EVENT_PINCH_START = 'gesturestart';
  11. EVENT_PINCH = 'gesturechange';
  12. EVENT_PINCH_END = 'gestureend';
  13. }
  14. $.Zoom = function(element, options) {
  15. var zoom = this;
  16. zoom.options = $.extend($.Zoom.defaults, options);
  17. zoom.wrapper = zoom.element = element;
  18. zoom.scroller = element.querySelector(SELECTOR_ZOOM_SCROLLER);
  19. zoom.scrollerStyle = zoom.scroller && zoom.scroller.style;
  20. zoom.zoomer = element.querySelector(SELECTOR_ZOOM);
  21. zoom.zoomerStyle = zoom.zoomer && zoom.zoomer.style;
  22. zoom.init = function() {
  23. //自动启用
  24. $.options.gestureConfig.pinch = true;
  25. $.options.gestureConfig.doubletap = true;
  26. zoom.initEvents();
  27. };
  28. zoom.initEvents = function(detach) {
  29. var action = detach ? 'removeEventListener' : 'addEventListener';
  30. var target = zoom.scroller;
  31. target[action](EVENT_PINCH_START, zoom.onPinchstart);
  32. target[action](EVENT_PINCH, zoom.onPinch);
  33. target[action](EVENT_PINCH_END, zoom.onPinchend);
  34. target[action]($.EVENT_START, zoom.onTouchstart);
  35. target[action]($.EVENT_MOVE, zoom.onTouchMove);
  36. target[action]($.EVENT_CANCEL, zoom.onTouchEnd);
  37. target[action]($.EVENT_END, zoom.onTouchEnd);
  38. target[action]('drag', zoom.dragEvent);
  39. target[action]('doubletap', zoom.doubleTapEvent);
  40. };
  41. zoom.dragEvent = function(e) {
  42. if (imageIsMoved || isGesturing) {
  43. e.stopPropagation();
  44. }
  45. };
  46. zoom.doubleTapEvent = function(e) {
  47. zoom.toggleZoom(e.detail.center);
  48. };
  49. zoom.transition = function(style, time) {
  50. time = time || 0;
  51. style['webkitTransitionDuration'] = time + 'ms';
  52. return zoom;
  53. };
  54. zoom.translate = function(style, x, y) {
  55. x = x || 0;
  56. y = y || 0;
  57. style['webkitTransform'] = 'translate3d(' + x + 'px,' + y + 'px,0px)';
  58. return zoom;
  59. };
  60. zoom.scale = function(style, scale) {
  61. scale = scale || 1;
  62. style['webkitTransform'] = 'translate3d(0,0,0) scale(' + scale + ')';
  63. return zoom;
  64. };
  65. zoom.scrollerTransition = function(time) {
  66. return zoom.transition(zoom.scrollerStyle, time);
  67. };
  68. zoom.scrollerTransform = function(x, y) {
  69. return zoom.translate(zoom.scrollerStyle, x, y);
  70. };
  71. zoom.zoomerTransition = function(time) {
  72. return zoom.transition(zoom.zoomerStyle, time);
  73. };
  74. zoom.zoomerTransform = function(scale) {
  75. return zoom.scale(zoom.zoomerStyle, scale);
  76. };
  77. // Gestures
  78. var scale = 1,
  79. currentScale = 1,
  80. isScaling = false,
  81. isGesturing = false;
  82. zoom.onPinchstart = function(e) {
  83. isGesturing = true;
  84. };
  85. zoom.onPinch = function(e) {
  86. if (!isScaling) {
  87. zoom.zoomerTransition(0);
  88. isScaling = true;
  89. }
  90. scale = (e.detail ? e.detail.scale : e.scale) * currentScale;
  91. if (scale > zoom.options.maxZoom) {
  92. scale = zoom.options.maxZoom - 1 + Math.pow((scale - zoom.options.maxZoom + 1), 0.5);
  93. }
  94. if (scale < zoom.options.minZoom) {
  95. scale = zoom.options.minZoom + 1 - Math.pow((zoom.options.minZoom - scale + 1), 0.5);
  96. }
  97. zoom.zoomerTransform(scale);
  98. };
  99. zoom.onPinchend = function(e) {
  100. scale = Math.max(Math.min(scale, zoom.options.maxZoom), zoom.options.minZoom);
  101. zoom.zoomerTransition(zoom.options.speed).zoomerTransform(scale);
  102. currentScale = scale;
  103. isScaling = false;
  104. };
  105. zoom.setZoom = function(newScale) {
  106. scale = currentScale = newScale;
  107. zoom.scrollerTransition(zoom.options.speed).scrollerTransform(0, 0);
  108. zoom.zoomerTransition(zoom.options.speed).zoomerTransform(scale);
  109. };
  110. zoom.toggleZoom = function(position, speed) {
  111. if (typeof position === 'number') {
  112. speed = position;
  113. position = undefined;
  114. }
  115. speed = typeof speed === 'undefined' ? zoom.options.speed : speed;
  116. if (scale && scale !== 1) {
  117. scale = currentScale = 1;
  118. zoom.scrollerTransition(speed).scrollerTransform(0, 0);
  119. } else {
  120. scale = currentScale = zoom.options.maxZoom;
  121. if (position) {
  122. var offset = $.offset(zoom.zoomer);
  123. var top = offset.top;
  124. var left = offset.left;
  125. var offsetX = (position.x - left) * scale;
  126. var offsetY = (position.y - top) * scale;
  127. this._cal();
  128. if (offsetX >= imageMaxX && offsetX <= (imageMaxX + wrapperWidth)) { //center
  129. offsetX = imageMaxX - offsetX + wrapperWidth / 2;
  130. } else if (offsetX < imageMaxX) { //left
  131. offsetX = imageMaxX - offsetX + wrapperWidth / 2;
  132. } else if (offsetX > (imageMaxX + wrapperWidth)) { //right
  133. offsetX = imageMaxX + wrapperWidth - offsetX - wrapperWidth / 2;
  134. }
  135. if (offsetY >= imageMaxY && offsetY <= (imageMaxY + wrapperHeight)) { //middle
  136. offsetY = imageMaxY - offsetY + wrapperHeight / 2;
  137. } else if (offsetY < imageMaxY) { //top
  138. offsetY = imageMaxY - offsetY + wrapperHeight / 2;
  139. } else if (offsetY > (imageMaxY + wrapperHeight)) { //bottom
  140. offsetY = imageMaxY + wrapperHeight - offsetY - wrapperHeight / 2;
  141. }
  142. offsetX = Math.min(Math.max(offsetX, imageMinX), imageMaxX);
  143. offsetY = Math.min(Math.max(offsetY, imageMinY), imageMaxY);
  144. zoom.scrollerTransition(speed).scrollerTransform(offsetX, offsetY);
  145. } else {
  146. zoom.scrollerTransition(speed).scrollerTransform(0, 0);
  147. }
  148. }
  149. zoom.zoomerTransition(speed).zoomerTransform(scale);
  150. };
  151. zoom._cal = function() {
  152. wrapperWidth = zoom.wrapper.offsetWidth;
  153. wrapperHeight = zoom.wrapper.offsetHeight;
  154. imageWidth = zoom.zoomer.offsetWidth;
  155. imageHeight = zoom.zoomer.offsetHeight;
  156. var scaledWidth = imageWidth * scale;
  157. var scaledHeight = imageHeight * scale;
  158. imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0);
  159. imageMaxX = -imageMinX;
  160. imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0);
  161. imageMaxY = -imageMinY;
  162. };
  163. var wrapperWidth, wrapperHeight, imageIsTouched, imageIsMoved, imageCurrentX, imageCurrentY, imageMinX, imageMinY, imageMaxX, imageMaxY, imageWidth, imageHeight, imageTouchesStart = {},
  164. imageTouchesCurrent = {},
  165. imageStartX, imageStartY, velocityPrevPositionX, velocityPrevTime, velocityX, velocityPrevPositionY, velocityY;
  166. zoom.onTouchstart = function(e) {
  167. e.preventDefault();
  168. imageIsTouched = true;
  169. imageTouchesStart.x = e.type === $.EVENT_START ? e.targetTouches[0].pageX : e.pageX;
  170. imageTouchesStart.y = e.type === $.EVENT_START ? e.targetTouches[0].pageY : e.pageY;
  171. };
  172. zoom.onTouchMove = function(e) {
  173. e.preventDefault();
  174. if (!imageIsTouched) return;
  175. if (!imageIsMoved) {
  176. wrapperWidth = zoom.wrapper.offsetWidth;
  177. wrapperHeight = zoom.wrapper.offsetHeight;
  178. imageWidth = zoom.zoomer.offsetWidth;
  179. imageHeight = zoom.zoomer.offsetHeight;
  180. var translate = $.parseTranslateMatrix($.getStyles(zoom.scroller, 'webkitTransform'));
  181. imageStartX = translate.x || 0;
  182. imageStartY = translate.y || 0;
  183. zoom.scrollerTransition(0);
  184. }
  185. var scaledWidth = imageWidth * scale;
  186. var scaledHeight = imageHeight * scale;
  187. if (scaledWidth < wrapperWidth && scaledHeight < wrapperHeight) return;
  188. imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0);
  189. imageMaxX = -imageMinX;
  190. imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0);
  191. imageMaxY = -imageMinY;
  192. imageTouchesCurrent.x = e.type === $.EVENT_MOVE ? e.targetTouches[0].pageX : e.pageX;
  193. imageTouchesCurrent.y = e.type === $.EVENT_MOVE ? e.targetTouches[0].pageY : e.pageY;
  194. if (!imageIsMoved && !isScaling) {
  195. // if (Math.abs(imageTouchesCurrent.y - imageTouchesStart.y) < Math.abs(imageTouchesCurrent.x - imageTouchesStart.x)) {
  196. //TODO 此处需要优化,当遇到长图,需要上下滚动时,下列判断会导致滚动不流畅
  197. if (
  198. (Math.floor(imageMinX) === Math.floor(imageStartX) && imageTouchesCurrent.x < imageTouchesStart.x) ||
  199. (Math.floor(imageMaxX) === Math.floor(imageStartX) && imageTouchesCurrent.x > imageTouchesStart.x)
  200. ) {
  201. imageIsTouched = false;
  202. return;
  203. }
  204. // }
  205. }
  206. imageIsMoved = true;
  207. imageCurrentX = imageTouchesCurrent.x - imageTouchesStart.x + imageStartX;
  208. imageCurrentY = imageTouchesCurrent.y - imageTouchesStart.y + imageStartY;
  209. if (imageCurrentX < imageMinX) {
  210. imageCurrentX = imageMinX + 1 - Math.pow((imageMinX - imageCurrentX + 1), 0.8);
  211. }
  212. if (imageCurrentX > imageMaxX) {
  213. imageCurrentX = imageMaxX - 1 + Math.pow((imageCurrentX - imageMaxX + 1), 0.8);
  214. }
  215. if (imageCurrentY < imageMinY) {
  216. imageCurrentY = imageMinY + 1 - Math.pow((imageMinY - imageCurrentY + 1), 0.8);
  217. }
  218. if (imageCurrentY > imageMaxY) {
  219. imageCurrentY = imageMaxY - 1 + Math.pow((imageCurrentY - imageMaxY + 1), 0.8);
  220. }
  221. //Velocity
  222. if (!velocityPrevPositionX) velocityPrevPositionX = imageTouchesCurrent.x;
  223. if (!velocityPrevPositionY) velocityPrevPositionY = imageTouchesCurrent.y;
  224. if (!velocityPrevTime) velocityPrevTime = $.now();
  225. velocityX = (imageTouchesCurrent.x - velocityPrevPositionX) / ($.now() - velocityPrevTime) / 2;
  226. velocityY = (imageTouchesCurrent.y - velocityPrevPositionY) / ($.now() - velocityPrevTime) / 2;
  227. if (Math.abs(imageTouchesCurrent.x - velocityPrevPositionX) < 2) velocityX = 0;
  228. if (Math.abs(imageTouchesCurrent.y - velocityPrevPositionY) < 2) velocityY = 0;
  229. velocityPrevPositionX = imageTouchesCurrent.x;
  230. velocityPrevPositionY = imageTouchesCurrent.y;
  231. velocityPrevTime = $.now();
  232. zoom.scrollerTransform(imageCurrentX, imageCurrentY);
  233. };
  234. zoom.onTouchEnd = function(e) {
  235. if (!e.touches.length) {
  236. isGesturing = false;
  237. }
  238. if (!imageIsTouched || !imageIsMoved) {
  239. imageIsTouched = false;
  240. imageIsMoved = false;
  241. return;
  242. }
  243. imageIsTouched = false;
  244. imageIsMoved = false;
  245. var momentumDurationX = 300;
  246. var momentumDurationY = 300;
  247. var momentumDistanceX = velocityX * momentumDurationX;
  248. var newPositionX = imageCurrentX + momentumDistanceX;
  249. var momentumDistanceY = velocityY * momentumDurationY;
  250. var newPositionY = imageCurrentY + momentumDistanceY;
  251. if (velocityX !== 0) momentumDurationX = Math.abs((newPositionX - imageCurrentX) / velocityX);
  252. if (velocityY !== 0) momentumDurationY = Math.abs((newPositionY - imageCurrentY) / velocityY);
  253. var momentumDuration = Math.max(momentumDurationX, momentumDurationY);
  254. imageCurrentX = newPositionX;
  255. imageCurrentY = newPositionY;
  256. var scaledWidth = imageWidth * scale;
  257. var scaledHeight = imageHeight * scale;
  258. imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0);
  259. imageMaxX = -imageMinX;
  260. imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0);
  261. imageMaxY = -imageMinY;
  262. imageCurrentX = Math.max(Math.min(imageCurrentX, imageMaxX), imageMinX);
  263. imageCurrentY = Math.max(Math.min(imageCurrentY, imageMaxY), imageMinY);
  264. zoom.scrollerTransition(momentumDuration).scrollerTransform(imageCurrentX, imageCurrentY);
  265. };
  266. zoom.destroy = function() {
  267. zoom.initEvents(true); //detach
  268. delete $.data[zoom.wrapper.getAttribute('data-zoomer')];
  269. zoom.wrapper.setAttribute('data-zoomer', '');
  270. }
  271. zoom.init();
  272. return zoom;
  273. };
  274. $.Zoom.defaults = {
  275. speed: 300,
  276. maxZoom: 3,
  277. minZoom: 1,
  278. };
  279. $.fn.zoom = function(options) {
  280. var zoomApis = [];
  281. this.each(function() {
  282. var zoomApi = null;
  283. var self = this;
  284. var id = self.getAttribute('data-zoomer');
  285. if (!id) {
  286. id = ++$.uuid;
  287. $.data[id] = zoomApi = new $.Zoom(self, options);
  288. self.setAttribute('data-zoomer', id);
  289. } else {
  290. zoomApi = $.data[id];
  291. }
  292. zoomApis.push(zoomApi);
  293. });
  294. return zoomApis.length === 1 ? zoomApis[0] : zoomApis;
  295. };
  296. })(mui, window);