zoom.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*=========================
  2. Zoom
  3. ===========================*/
  4. s.zoom = {
  5. // "Global" Props
  6. scale: 1,
  7. currentScale: 1,
  8. isScaling: false,
  9. gesture: {
  10. slide: undefined,
  11. slideWidth: undefined,
  12. slideHeight: undefined,
  13. image: undefined,
  14. imageWrap: undefined,
  15. zoomMax: s.params.zoomMax
  16. },
  17. image: {
  18. isTouched: undefined,
  19. isMoved: undefined,
  20. currentX: undefined,
  21. currentY: undefined,
  22. minX: undefined,
  23. minY: undefined,
  24. maxX: undefined,
  25. maxY: undefined,
  26. width: undefined,
  27. height: undefined,
  28. startX: undefined,
  29. startY: undefined,
  30. touchesStart: {},
  31. touchesCurrent: {}
  32. },
  33. velocity: {
  34. x: undefined,
  35. y: undefined,
  36. prevPositionX: undefined,
  37. prevPositionY: undefined,
  38. prevTime: undefined
  39. },
  40. // Calc Scale From Multi-touches
  41. getDistanceBetweenTouches: function (e) {
  42. if (e.targetTouches.length < 2) return 1;
  43. var x1 = e.targetTouches[0].pageX,
  44. y1 = e.targetTouches[0].pageY,
  45. x2 = e.targetTouches[1].pageX,
  46. y2 = e.targetTouches[1].pageY;
  47. var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  48. return distance;
  49. },
  50. // Events
  51. onGestureStart: function (e) {
  52. var z = s.zoom;
  53. if (!s.support.gestures) {
  54. if (e.type !== 'touchstart' || e.type === 'touchstart' && e.targetTouches.length < 2) {
  55. return;
  56. }
  57. z.gesture.scaleStart = z.getDistanceBetweenTouches(e);
  58. }
  59. if (!z.gesture.slide || !z.gesture.slide.length) {
  60. z.gesture.slide = $(this);
  61. if (z.gesture.slide.length === 0) z.gesture.slide = s.slides.eq(s.activeIndex);
  62. z.gesture.image = z.gesture.slide.find('img, svg, canvas');
  63. z.gesture.imageWrap = z.gesture.image.parent('.' + s.params.zoomContainerClass);
  64. z.gesture.zoomMax = z.gesture.imageWrap.attr('data-swiper-zoom') || s.params.zoomMax ;
  65. if (z.gesture.imageWrap.length === 0) {
  66. z.gesture.image = undefined;
  67. return;
  68. }
  69. }
  70. z.gesture.image.transition(0);
  71. z.isScaling = true;
  72. },
  73. onGestureChange: function (e) {
  74. var z = s.zoom;
  75. if (!s.support.gestures) {
  76. if (e.type !== 'touchmove' || e.type === 'touchmove' && e.targetTouches.length < 2) {
  77. return;
  78. }
  79. z.gesture.scaleMove = z.getDistanceBetweenTouches(e);
  80. }
  81. if (!z.gesture.image || z.gesture.image.length === 0) return;
  82. if (s.support.gestures) {
  83. z.scale = e.scale * z.currentScale;
  84. }
  85. else {
  86. z.scale = (z.gesture.scaleMove / z.gesture.scaleStart) * z.currentScale;
  87. }
  88. if (z.scale > z.gesture.zoomMax) {
  89. z.scale = z.gesture.zoomMax - 1 + Math.pow((z.scale - z.gesture.zoomMax + 1), 0.5);
  90. }
  91. if (z.scale < s.params.zoomMin) {
  92. z.scale = s.params.zoomMin + 1 - Math.pow((s.params.zoomMin - z.scale + 1), 0.5);
  93. }
  94. z.gesture.image.transform('translate3d(0,0,0) scale(' + z.scale + ')');
  95. },
  96. onGestureEnd: function (e) {
  97. var z = s.zoom;
  98. if (!s.support.gestures) {
  99. if (e.type !== 'touchend' || e.type === 'touchend' && e.changedTouches.length < 2) {
  100. return;
  101. }
  102. }
  103. if (!z.gesture.image || z.gesture.image.length === 0) return;
  104. z.scale = Math.max(Math.min(z.scale, z.gesture.zoomMax), s.params.zoomMin);
  105. z.gesture.image.transition(s.params.speed).transform('translate3d(0,0,0) scale(' + z.scale + ')');
  106. z.currentScale = z.scale;
  107. z.isScaling = false;
  108. if (z.scale === 1) z.gesture.slide = undefined;
  109. },
  110. onTouchStart: function (s, e) {
  111. var z = s.zoom;
  112. if (!z.gesture.image || z.gesture.image.length === 0) return;
  113. if (z.image.isTouched) return;
  114. if (s.device.os === 'android') e.preventDefault();
  115. z.image.isTouched = true;
  116. z.image.touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
  117. z.image.touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
  118. },
  119. onTouchMove: function (e) {
  120. var z = s.zoom;
  121. if (!z.gesture.image || z.gesture.image.length === 0) return;
  122. s.allowClick = false;
  123. if (!z.image.isTouched || !z.gesture.slide) return;
  124. if (!z.image.isMoved) {
  125. z.image.width = z.gesture.image[0].offsetWidth;
  126. z.image.height = z.gesture.image[0].offsetHeight;
  127. z.image.startX = s.getTranslate(z.gesture.imageWrap[0], 'x') || 0;
  128. z.image.startY = s.getTranslate(z.gesture.imageWrap[0], 'y') || 0;
  129. z.gesture.slideWidth = z.gesture.slide[0].offsetWidth;
  130. z.gesture.slideHeight = z.gesture.slide[0].offsetHeight;
  131. z.gesture.imageWrap.transition(0);
  132. if (s.rtl) z.image.startX = -z.image.startX;
  133. if (s.rtl) z.image.startY = -z.image.startY;
  134. }
  135. // Define if we need image drag
  136. var scaledWidth = z.image.width * z.scale;
  137. var scaledHeight = z.image.height * z.scale;
  138. if (scaledWidth < z.gesture.slideWidth && scaledHeight < z.gesture.slideHeight) return;
  139. z.image.minX = Math.min((z.gesture.slideWidth / 2 - scaledWidth / 2), 0);
  140. z.image.maxX = -z.image.minX;
  141. z.image.minY = Math.min((z.gesture.slideHeight / 2 - scaledHeight / 2), 0);
  142. z.image.maxY = -z.image.minY;
  143. z.image.touchesCurrent.x = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
  144. z.image.touchesCurrent.y = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
  145. if (!z.image.isMoved && !z.isScaling) {
  146. if (s.isHorizontal() &&
  147. (Math.floor(z.image.minX) === Math.floor(z.image.startX) && z.image.touchesCurrent.x < z.image.touchesStart.x) ||
  148. (Math.floor(z.image.maxX) === Math.floor(z.image.startX) && z.image.touchesCurrent.x > z.image.touchesStart.x)
  149. ) {
  150. z.image.isTouched = false;
  151. return;
  152. }
  153. else if (!s.isHorizontal() &&
  154. (Math.floor(z.image.minY) === Math.floor(z.image.startY) && z.image.touchesCurrent.y < z.image.touchesStart.y) ||
  155. (Math.floor(z.image.maxY) === Math.floor(z.image.startY) && z.image.touchesCurrent.y > z.image.touchesStart.y)
  156. ) {
  157. z.image.isTouched = false;
  158. return;
  159. }
  160. }
  161. e.preventDefault();
  162. e.stopPropagation();
  163. z.image.isMoved = true;
  164. z.image.currentX = z.image.touchesCurrent.x - z.image.touchesStart.x + z.image.startX;
  165. z.image.currentY = z.image.touchesCurrent.y - z.image.touchesStart.y + z.image.startY;
  166. if (z.image.currentX < z.image.minX) {
  167. z.image.currentX = z.image.minX + 1 - Math.pow((z.image.minX - z.image.currentX + 1), 0.8);
  168. }
  169. if (z.image.currentX > z.image.maxX) {
  170. z.image.currentX = z.image.maxX - 1 + Math.pow((z.image.currentX - z.image.maxX + 1), 0.8);
  171. }
  172. if (z.image.currentY < z.image.minY) {
  173. z.image.currentY = z.image.minY + 1 - Math.pow((z.image.minY - z.image.currentY + 1), 0.8);
  174. }
  175. if (z.image.currentY > z.image.maxY) {
  176. z.image.currentY = z.image.maxY - 1 + Math.pow((z.image.currentY - z.image.maxY + 1), 0.8);
  177. }
  178. //Velocity
  179. if (!z.velocity.prevPositionX) z.velocity.prevPositionX = z.image.touchesCurrent.x;
  180. if (!z.velocity.prevPositionY) z.velocity.prevPositionY = z.image.touchesCurrent.y;
  181. if (!z.velocity.prevTime) z.velocity.prevTime = Date.now();
  182. z.velocity.x = (z.image.touchesCurrent.x - z.velocity.prevPositionX) / (Date.now() - z.velocity.prevTime) / 2;
  183. z.velocity.y = (z.image.touchesCurrent.y - z.velocity.prevPositionY) / (Date.now() - z.velocity.prevTime) / 2;
  184. if (Math.abs(z.image.touchesCurrent.x - z.velocity.prevPositionX) < 2) z.velocity.x = 0;
  185. if (Math.abs(z.image.touchesCurrent.y - z.velocity.prevPositionY) < 2) z.velocity.y = 0;
  186. z.velocity.prevPositionX = z.image.touchesCurrent.x;
  187. z.velocity.prevPositionY = z.image.touchesCurrent.y;
  188. z.velocity.prevTime = Date.now();
  189. z.gesture.imageWrap.transform('translate3d(' + z.image.currentX + 'px, ' + z.image.currentY + 'px,0)');
  190. },
  191. onTouchEnd: function (s, e) {
  192. var z = s.zoom;
  193. if (!z.gesture.image || z.gesture.image.length === 0) return;
  194. if (!z.image.isTouched || !z.image.isMoved) {
  195. z.image.isTouched = false;
  196. z.image.isMoved = false;
  197. return;
  198. }
  199. z.image.isTouched = false;
  200. z.image.isMoved = false;
  201. var momentumDurationX = 300;
  202. var momentumDurationY = 300;
  203. var momentumDistanceX = z.velocity.x * momentumDurationX;
  204. var newPositionX = z.image.currentX + momentumDistanceX;
  205. var momentumDistanceY = z.velocity.y * momentumDurationY;
  206. var newPositionY = z.image.currentY + momentumDistanceY;
  207. //Fix duration
  208. if (z.velocity.x !== 0) momentumDurationX = Math.abs((newPositionX - z.image.currentX) / z.velocity.x);
  209. if (z.velocity.y !== 0) momentumDurationY = Math.abs((newPositionY - z.image.currentY) / z.velocity.y);
  210. var momentumDuration = Math.max(momentumDurationX, momentumDurationY);
  211. z.image.currentX = newPositionX;
  212. z.image.currentY = newPositionY;
  213. // Define if we need image drag
  214. var scaledWidth = z.image.width * z.scale;
  215. var scaledHeight = z.image.height * z.scale;
  216. z.image.minX = Math.min((z.gesture.slideWidth / 2 - scaledWidth / 2), 0);
  217. z.image.maxX = -z.image.minX;
  218. z.image.minY = Math.min((z.gesture.slideHeight / 2 - scaledHeight / 2), 0);
  219. z.image.maxY = -z.image.minY;
  220. z.image.currentX = Math.max(Math.min(z.image.currentX, z.image.maxX), z.image.minX);
  221. z.image.currentY = Math.max(Math.min(z.image.currentY, z.image.maxY), z.image.minY);
  222. z.gesture.imageWrap.transition(momentumDuration).transform('translate3d(' + z.image.currentX + 'px, ' + z.image.currentY + 'px,0)');
  223. },
  224. onTransitionEnd: function (s) {
  225. var z = s.zoom;
  226. if (z.gesture.slide && s.previousIndex !== s.activeIndex) {
  227. z.gesture.image.transform('translate3d(0,0,0) scale(1)');
  228. z.gesture.imageWrap.transform('translate3d(0,0,0)');
  229. z.gesture.slide = z.gesture.image = z.gesture.imageWrap = undefined;
  230. z.scale = z.currentScale = 1;
  231. }
  232. },
  233. // Toggle Zoom
  234. toggleZoom: function (s, e) {
  235. var z = s.zoom;
  236. if (!z.gesture.slide) {
  237. z.gesture.slide = s.clickedSlide ? $(s.clickedSlide) : s.slides.eq(s.activeIndex);
  238. z.gesture.image = z.gesture.slide.find('img, svg, canvas');
  239. z.gesture.imageWrap = z.gesture.image.parent('.' + s.params.zoomContainerClass);
  240. }
  241. if (!z.gesture.image || z.gesture.image.length === 0) return;
  242. var touchX, touchY, offsetX, offsetY, diffX, diffY, translateX, translateY, imageWidth, imageHeight, scaledWidth, scaledHeight, translateMinX, translateMinY, translateMaxX, translateMaxY, slideWidth, slideHeight;
  243. if (typeof z.image.touchesStart.x === 'undefined' && e) {
  244. touchX = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX;
  245. touchY = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY;
  246. }
  247. else {
  248. touchX = z.image.touchesStart.x;
  249. touchY = z.image.touchesStart.y;
  250. }
  251. if (z.scale && z.scale !== 1) {
  252. // Zoom Out
  253. z.scale = z.currentScale = 1;
  254. z.gesture.imageWrap.transition(300).transform('translate3d(0,0,0)');
  255. z.gesture.image.transition(300).transform('translate3d(0,0,0) scale(1)');
  256. z.gesture.slide = undefined;
  257. }
  258. else {
  259. // Zoom In
  260. z.scale = z.currentScale = z.gesture.imageWrap.attr('data-swiper-zoom') || s.params.zoomMax;
  261. if (e) {
  262. slideWidth = z.gesture.slide[0].offsetWidth;
  263. slideHeight = z.gesture.slide[0].offsetHeight;
  264. offsetX = z.gesture.slide.offset().left;
  265. offsetY = z.gesture.slide.offset().top;
  266. diffX = offsetX + slideWidth/2 - touchX;
  267. diffY = offsetY + slideHeight/2 - touchY;
  268. imageWidth = z.gesture.image[0].offsetWidth;
  269. imageHeight = z.gesture.image[0].offsetHeight;
  270. scaledWidth = imageWidth * z.scale;
  271. scaledHeight = imageHeight * z.scale;
  272. translateMinX = Math.min((slideWidth / 2 - scaledWidth / 2), 0);
  273. translateMinY = Math.min((slideHeight / 2 - scaledHeight / 2), 0);
  274. translateMaxX = -translateMinX;
  275. translateMaxY = -translateMinY;
  276. translateX = diffX * z.scale;
  277. translateY = diffY * z.scale;
  278. if (translateX < translateMinX) {
  279. translateX = translateMinX;
  280. }
  281. if (translateX > translateMaxX) {
  282. translateX = translateMaxX;
  283. }
  284. if (translateY < translateMinY) {
  285. translateY = translateMinY;
  286. }
  287. if (translateY > translateMaxY) {
  288. translateY = translateMaxY;
  289. }
  290. }
  291. else {
  292. translateX = 0;
  293. translateY = 0;
  294. }
  295. z.gesture.imageWrap.transition(300).transform('translate3d(' + translateX + 'px, ' + translateY + 'px,0)');
  296. z.gesture.image.transition(300).transform('translate3d(0,0,0) scale(' + z.scale + ')');
  297. }
  298. },
  299. // Attach/Detach Events
  300. attachEvents: function (detach) {
  301. var action = detach ? 'off' : 'on';
  302. if (s.params.zoom) {
  303. var target = s.slides;
  304. var passiveListener = s.touchEvents.start === 'touchstart' && s.support.passiveListener && s.params.passiveListeners ? {passive: true, capture: false} : false;
  305. // Scale image
  306. if (s.support.gestures) {
  307. s.slides[action]('gesturestart', s.zoom.onGestureStart, passiveListener);
  308. s.slides[action]('gesturechange', s.zoom.onGestureChange, passiveListener);
  309. s.slides[action]('gestureend', s.zoom.onGestureEnd, passiveListener);
  310. }
  311. else if (s.touchEvents.start === 'touchstart') {
  312. s.slides[action](s.touchEvents.start, s.zoom.onGestureStart, passiveListener);
  313. s.slides[action](s.touchEvents.move, s.zoom.onGestureChange, passiveListener);
  314. s.slides[action](s.touchEvents.end, s.zoom.onGestureEnd, passiveListener);
  315. }
  316. // Move image
  317. s[action]('touchStart', s.zoom.onTouchStart);
  318. s.slides.each(function (index, slide){
  319. if ($(slide).find('.' + s.params.zoomContainerClass).length > 0) {
  320. $(slide)[action](s.touchEvents.move, s.zoom.onTouchMove);
  321. }
  322. });
  323. s[action]('touchEnd', s.zoom.onTouchEnd);
  324. // Scale Out
  325. s[action]('transitionEnd', s.zoom.onTransitionEnd);
  326. if (s.params.zoomToggle) {
  327. s.on('doubleTap', s.zoom.toggleZoom);
  328. }
  329. }
  330. },
  331. init: function () {
  332. s.zoom.attachEvents();
  333. },
  334. destroy: function () {
  335. s.zoom.attachEvents(true);
  336. }
  337. };