zoom.mjs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. import { a as getWindow } from '../shared/ssr-window.esm.mjs';
  2. import { e as elementChildren, b as elementParents, d as elementOffset, k as getTranslate } from '../shared/utils.mjs';
  3. function Zoom(_ref) {
  4. let {
  5. swiper,
  6. extendParams,
  7. on,
  8. emit
  9. } = _ref;
  10. const window = getWindow();
  11. extendParams({
  12. zoom: {
  13. enabled: false,
  14. limitToOriginalSize: false,
  15. maxRatio: 3,
  16. minRatio: 1,
  17. panOnMouseMove: false,
  18. toggle: true,
  19. containerClass: 'swiper-zoom-container',
  20. zoomedSlideClass: 'swiper-slide-zoomed'
  21. }
  22. });
  23. swiper.zoom = {
  24. enabled: false
  25. };
  26. let currentScale = 1;
  27. let isScaling = false;
  28. let isPanningWithMouse = false;
  29. let mousePanStart = {
  30. x: 0,
  31. y: 0
  32. };
  33. const mousePanSensitivity = -3; // Negative to invert pan direction
  34. let fakeGestureTouched;
  35. let fakeGestureMoved;
  36. const evCache = [];
  37. const gesture = {
  38. originX: 0,
  39. originY: 0,
  40. slideEl: undefined,
  41. slideWidth: undefined,
  42. slideHeight: undefined,
  43. imageEl: undefined,
  44. imageWrapEl: undefined,
  45. maxRatio: 3
  46. };
  47. const image = {
  48. isTouched: undefined,
  49. isMoved: undefined,
  50. currentX: undefined,
  51. currentY: undefined,
  52. minX: undefined,
  53. minY: undefined,
  54. maxX: undefined,
  55. maxY: undefined,
  56. width: undefined,
  57. height: undefined,
  58. startX: undefined,
  59. startY: undefined,
  60. touchesStart: {},
  61. touchesCurrent: {}
  62. };
  63. const velocity = {
  64. x: undefined,
  65. y: undefined,
  66. prevPositionX: undefined,
  67. prevPositionY: undefined,
  68. prevTime: undefined
  69. };
  70. let scale = 1;
  71. Object.defineProperty(swiper.zoom, 'scale', {
  72. get() {
  73. return scale;
  74. },
  75. set(value) {
  76. if (scale !== value) {
  77. const imageEl = gesture.imageEl;
  78. const slideEl = gesture.slideEl;
  79. emit('zoomChange', value, imageEl, slideEl);
  80. }
  81. scale = value;
  82. }
  83. });
  84. function getDistanceBetweenTouches() {
  85. if (evCache.length < 2) return 1;
  86. const x1 = evCache[0].pageX;
  87. const y1 = evCache[0].pageY;
  88. const x2 = evCache[1].pageX;
  89. const y2 = evCache[1].pageY;
  90. const distance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
  91. return distance;
  92. }
  93. function getMaxRatio() {
  94. const params = swiper.params.zoom;
  95. const maxRatio = gesture.imageWrapEl.getAttribute('data-swiper-zoom') || params.maxRatio;
  96. if (params.limitToOriginalSize && gesture.imageEl && gesture.imageEl.naturalWidth) {
  97. const imageMaxRatio = gesture.imageEl.naturalWidth / gesture.imageEl.offsetWidth;
  98. return Math.min(imageMaxRatio, maxRatio);
  99. }
  100. return maxRatio;
  101. }
  102. function getScaleOrigin() {
  103. if (evCache.length < 2) return {
  104. x: null,
  105. y: null
  106. };
  107. const box = gesture.imageEl.getBoundingClientRect();
  108. return [(evCache[0].pageX + (evCache[1].pageX - evCache[0].pageX) / 2 - box.x - window.scrollX) / currentScale, (evCache[0].pageY + (evCache[1].pageY - evCache[0].pageY) / 2 - box.y - window.scrollY) / currentScale];
  109. }
  110. function getSlideSelector() {
  111. return swiper.isElement ? `swiper-slide` : `.${swiper.params.slideClass}`;
  112. }
  113. function eventWithinSlide(e) {
  114. const slideSelector = getSlideSelector();
  115. if (e.target.matches(slideSelector)) return true;
  116. if (swiper.slides.filter(slideEl => slideEl.contains(e.target)).length > 0) return true;
  117. return false;
  118. }
  119. function eventWithinZoomContainer(e) {
  120. const selector = `.${swiper.params.zoom.containerClass}`;
  121. if (e.target.matches(selector)) return true;
  122. if ([...swiper.hostEl.querySelectorAll(selector)].filter(containerEl => containerEl.contains(e.target)).length > 0) return true;
  123. return false;
  124. }
  125. // Events
  126. function onGestureStart(e) {
  127. if (e.pointerType === 'mouse') {
  128. evCache.splice(0, evCache.length);
  129. }
  130. if (!eventWithinSlide(e)) return;
  131. const params = swiper.params.zoom;
  132. fakeGestureTouched = false;
  133. fakeGestureMoved = false;
  134. evCache.push(e);
  135. if (evCache.length < 2) {
  136. return;
  137. }
  138. fakeGestureTouched = true;
  139. gesture.scaleStart = getDistanceBetweenTouches();
  140. if (!gesture.slideEl) {
  141. gesture.slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`);
  142. if (!gesture.slideEl) gesture.slideEl = swiper.slides[swiper.activeIndex];
  143. let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`);
  144. if (imageEl) {
  145. imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0];
  146. }
  147. gesture.imageEl = imageEl;
  148. if (imageEl) {
  149. gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0];
  150. } else {
  151. gesture.imageWrapEl = undefined;
  152. }
  153. if (!gesture.imageWrapEl) {
  154. gesture.imageEl = undefined;
  155. return;
  156. }
  157. gesture.maxRatio = getMaxRatio();
  158. }
  159. if (gesture.imageEl) {
  160. const [originX, originY] = getScaleOrigin();
  161. gesture.originX = originX;
  162. gesture.originY = originY;
  163. gesture.imageEl.style.transitionDuration = '0ms';
  164. }
  165. isScaling = true;
  166. }
  167. function onGestureChange(e) {
  168. if (!eventWithinSlide(e)) return;
  169. const params = swiper.params.zoom;
  170. const zoom = swiper.zoom;
  171. const pointerIndex = evCache.findIndex(cachedEv => cachedEv.pointerId === e.pointerId);
  172. if (pointerIndex >= 0) evCache[pointerIndex] = e;
  173. if (evCache.length < 2) {
  174. return;
  175. }
  176. fakeGestureMoved = true;
  177. gesture.scaleMove = getDistanceBetweenTouches();
  178. if (!gesture.imageEl) {
  179. return;
  180. }
  181. zoom.scale = gesture.scaleMove / gesture.scaleStart * currentScale;
  182. if (zoom.scale > gesture.maxRatio) {
  183. zoom.scale = gesture.maxRatio - 1 + (zoom.scale - gesture.maxRatio + 1) ** 0.5;
  184. }
  185. if (zoom.scale < params.minRatio) {
  186. zoom.scale = params.minRatio + 1 - (params.minRatio - zoom.scale + 1) ** 0.5;
  187. }
  188. gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`;
  189. }
  190. function onGestureEnd(e) {
  191. if (!eventWithinSlide(e)) return;
  192. if (e.pointerType === 'mouse' && e.type === 'pointerout') return;
  193. const params = swiper.params.zoom;
  194. const zoom = swiper.zoom;
  195. const pointerIndex = evCache.findIndex(cachedEv => cachedEv.pointerId === e.pointerId);
  196. if (pointerIndex >= 0) evCache.splice(pointerIndex, 1);
  197. if (!fakeGestureTouched || !fakeGestureMoved) {
  198. return;
  199. }
  200. fakeGestureTouched = false;
  201. fakeGestureMoved = false;
  202. if (!gesture.imageEl) return;
  203. zoom.scale = Math.max(Math.min(zoom.scale, gesture.maxRatio), params.minRatio);
  204. gesture.imageEl.style.transitionDuration = `${swiper.params.speed}ms`;
  205. gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`;
  206. currentScale = zoom.scale;
  207. isScaling = false;
  208. if (zoom.scale > 1 && gesture.slideEl) {
  209. gesture.slideEl.classList.add(`${params.zoomedSlideClass}`);
  210. } else if (zoom.scale <= 1 && gesture.slideEl) {
  211. gesture.slideEl.classList.remove(`${params.zoomedSlideClass}`);
  212. }
  213. if (zoom.scale === 1) {
  214. gesture.originX = 0;
  215. gesture.originY = 0;
  216. gesture.slideEl = undefined;
  217. }
  218. }
  219. let allowTouchMoveTimeout;
  220. function allowTouchMove() {
  221. swiper.touchEventsData.preventTouchMoveFromPointerMove = false;
  222. }
  223. function preventTouchMove() {
  224. clearTimeout(allowTouchMoveTimeout);
  225. swiper.touchEventsData.preventTouchMoveFromPointerMove = true;
  226. allowTouchMoveTimeout = setTimeout(() => {
  227. if (swiper.destroyed) return;
  228. allowTouchMove();
  229. });
  230. }
  231. function onTouchStart(e) {
  232. const device = swiper.device;
  233. if (!gesture.imageEl) return;
  234. if (image.isTouched) return;
  235. if (device.android && e.cancelable) e.preventDefault();
  236. image.isTouched = true;
  237. const event = evCache.length > 0 ? evCache[0] : e;
  238. image.touchesStart.x = event.pageX;
  239. image.touchesStart.y = event.pageY;
  240. }
  241. function onTouchMove(e) {
  242. const isMouseEvent = e.pointerType === 'mouse';
  243. const isMousePan = isMouseEvent && swiper.params.zoom.panOnMouseMove;
  244. if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) {
  245. return;
  246. }
  247. const zoom = swiper.zoom;
  248. if (!gesture.imageEl) {
  249. return;
  250. }
  251. if (!image.isTouched || !gesture.slideEl) {
  252. if (isMousePan) onMouseMove(e);
  253. return;
  254. }
  255. if (isMousePan) {
  256. onMouseMove(e);
  257. return;
  258. }
  259. if (!image.isMoved) {
  260. image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth;
  261. image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight;
  262. image.startX = getTranslate(gesture.imageWrapEl, 'x') || 0;
  263. image.startY = getTranslate(gesture.imageWrapEl, 'y') || 0;
  264. gesture.slideWidth = gesture.slideEl.offsetWidth;
  265. gesture.slideHeight = gesture.slideEl.offsetHeight;
  266. gesture.imageWrapEl.style.transitionDuration = '0ms';
  267. }
  268. // Define if we need image drag
  269. const scaledWidth = image.width * zoom.scale;
  270. const scaledHeight = image.height * zoom.scale;
  271. image.minX = Math.min(gesture.slideWidth / 2 - scaledWidth / 2, 0);
  272. image.maxX = -image.minX;
  273. image.minY = Math.min(gesture.slideHeight / 2 - scaledHeight / 2, 0);
  274. image.maxY = -image.minY;
  275. image.touchesCurrent.x = evCache.length > 0 ? evCache[0].pageX : e.pageX;
  276. image.touchesCurrent.y = evCache.length > 0 ? evCache[0].pageY : e.pageY;
  277. const touchesDiff = Math.max(Math.abs(image.touchesCurrent.x - image.touchesStart.x), Math.abs(image.touchesCurrent.y - image.touchesStart.y));
  278. if (touchesDiff > 5) {
  279. swiper.allowClick = false;
  280. }
  281. if (!image.isMoved && !isScaling) {
  282. if (swiper.isHorizontal() && (Math.floor(image.minX) === Math.floor(image.startX) && image.touchesCurrent.x < image.touchesStart.x || Math.floor(image.maxX) === Math.floor(image.startX) && image.touchesCurrent.x > image.touchesStart.x)) {
  283. image.isTouched = false;
  284. allowTouchMove();
  285. return;
  286. }
  287. if (!swiper.isHorizontal() && (Math.floor(image.minY) === Math.floor(image.startY) && image.touchesCurrent.y < image.touchesStart.y || Math.floor(image.maxY) === Math.floor(image.startY) && image.touchesCurrent.y > image.touchesStart.y)) {
  288. image.isTouched = false;
  289. allowTouchMove();
  290. return;
  291. }
  292. }
  293. if (e.cancelable) {
  294. e.preventDefault();
  295. }
  296. e.stopPropagation();
  297. preventTouchMove();
  298. image.isMoved = true;
  299. const scaleRatio = (zoom.scale - currentScale) / (gesture.maxRatio - swiper.params.zoom.minRatio);
  300. const {
  301. originX,
  302. originY
  303. } = gesture;
  304. image.currentX = image.touchesCurrent.x - image.touchesStart.x + image.startX + scaleRatio * (image.width - originX * 2);
  305. image.currentY = image.touchesCurrent.y - image.touchesStart.y + image.startY + scaleRatio * (image.height - originY * 2);
  306. if (image.currentX < image.minX) {
  307. image.currentX = image.minX + 1 - (image.minX - image.currentX + 1) ** 0.8;
  308. }
  309. if (image.currentX > image.maxX) {
  310. image.currentX = image.maxX - 1 + (image.currentX - image.maxX + 1) ** 0.8;
  311. }
  312. if (image.currentY < image.minY) {
  313. image.currentY = image.minY + 1 - (image.minY - image.currentY + 1) ** 0.8;
  314. }
  315. if (image.currentY > image.maxY) {
  316. image.currentY = image.maxY - 1 + (image.currentY - image.maxY + 1) ** 0.8;
  317. }
  318. // Velocity
  319. if (!velocity.prevPositionX) velocity.prevPositionX = image.touchesCurrent.x;
  320. if (!velocity.prevPositionY) velocity.prevPositionY = image.touchesCurrent.y;
  321. if (!velocity.prevTime) velocity.prevTime = Date.now();
  322. velocity.x = (image.touchesCurrent.x - velocity.prevPositionX) / (Date.now() - velocity.prevTime) / 2;
  323. velocity.y = (image.touchesCurrent.y - velocity.prevPositionY) / (Date.now() - velocity.prevTime) / 2;
  324. if (Math.abs(image.touchesCurrent.x - velocity.prevPositionX) < 2) velocity.x = 0;
  325. if (Math.abs(image.touchesCurrent.y - velocity.prevPositionY) < 2) velocity.y = 0;
  326. velocity.prevPositionX = image.touchesCurrent.x;
  327. velocity.prevPositionY = image.touchesCurrent.y;
  328. velocity.prevTime = Date.now();
  329. gesture.imageWrapEl.style.transform = `translate3d(${image.currentX}px, ${image.currentY}px,0)`;
  330. }
  331. function onTouchEnd() {
  332. const zoom = swiper.zoom;
  333. evCache.length = 0;
  334. if (!gesture.imageEl) return;
  335. if (!image.isTouched || !image.isMoved) {
  336. image.isTouched = false;
  337. image.isMoved = false;
  338. return;
  339. }
  340. image.isTouched = false;
  341. image.isMoved = false;
  342. let momentumDurationX = 300;
  343. let momentumDurationY = 300;
  344. const momentumDistanceX = velocity.x * momentumDurationX;
  345. const newPositionX = image.currentX + momentumDistanceX;
  346. const momentumDistanceY = velocity.y * momentumDurationY;
  347. const newPositionY = image.currentY + momentumDistanceY;
  348. // Fix duration
  349. if (velocity.x !== 0) momentumDurationX = Math.abs((newPositionX - image.currentX) / velocity.x);
  350. if (velocity.y !== 0) momentumDurationY = Math.abs((newPositionY - image.currentY) / velocity.y);
  351. const momentumDuration = Math.max(momentumDurationX, momentumDurationY);
  352. image.currentX = newPositionX;
  353. image.currentY = newPositionY;
  354. // Define if we need image drag
  355. const scaledWidth = image.width * zoom.scale;
  356. const scaledHeight = image.height * zoom.scale;
  357. image.minX = Math.min(gesture.slideWidth / 2 - scaledWidth / 2, 0);
  358. image.maxX = -image.minX;
  359. image.minY = Math.min(gesture.slideHeight / 2 - scaledHeight / 2, 0);
  360. image.maxY = -image.minY;
  361. image.currentX = Math.max(Math.min(image.currentX, image.maxX), image.minX);
  362. image.currentY = Math.max(Math.min(image.currentY, image.maxY), image.minY);
  363. gesture.imageWrapEl.style.transitionDuration = `${momentumDuration}ms`;
  364. gesture.imageWrapEl.style.transform = `translate3d(${image.currentX}px, ${image.currentY}px,0)`;
  365. }
  366. function onTransitionEnd() {
  367. const zoom = swiper.zoom;
  368. if (gesture.slideEl && swiper.activeIndex !== swiper.slides.indexOf(gesture.slideEl)) {
  369. if (gesture.imageEl) {
  370. gesture.imageEl.style.transform = 'translate3d(0,0,0) scale(1)';
  371. }
  372. if (gesture.imageWrapEl) {
  373. gesture.imageWrapEl.style.transform = 'translate3d(0,0,0)';
  374. }
  375. gesture.slideEl.classList.remove(`${swiper.params.zoom.zoomedSlideClass}`);
  376. zoom.scale = 1;
  377. currentScale = 1;
  378. gesture.slideEl = undefined;
  379. gesture.imageEl = undefined;
  380. gesture.imageWrapEl = undefined;
  381. gesture.originX = 0;
  382. gesture.originY = 0;
  383. }
  384. }
  385. function onMouseMove(e) {
  386. // Only pan if zoomed in and mouse panning is enabled
  387. if (currentScale <= 1 || !gesture.imageWrapEl) return;
  388. if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) return;
  389. const currentTransform = window.getComputedStyle(gesture.imageWrapEl).transform;
  390. const matrix = new window.DOMMatrix(currentTransform);
  391. if (!isPanningWithMouse) {
  392. isPanningWithMouse = true;
  393. mousePanStart.x = e.clientX;
  394. mousePanStart.y = e.clientY;
  395. image.startX = matrix.e;
  396. image.startY = matrix.f;
  397. image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth;
  398. image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight;
  399. gesture.slideWidth = gesture.slideEl.offsetWidth;
  400. gesture.slideHeight = gesture.slideEl.offsetHeight;
  401. return;
  402. }
  403. const deltaX = (e.clientX - mousePanStart.x) * mousePanSensitivity;
  404. const deltaY = (e.clientY - mousePanStart.y) * mousePanSensitivity;
  405. const scaledWidth = image.width * currentScale;
  406. const scaledHeight = image.height * currentScale;
  407. const slideWidth = gesture.slideWidth;
  408. const slideHeight = gesture.slideHeight;
  409. const minX = Math.min(slideWidth / 2 - scaledWidth / 2, 0);
  410. const maxX = -minX;
  411. const minY = Math.min(slideHeight / 2 - scaledHeight / 2, 0);
  412. const maxY = -minY;
  413. const newX = Math.max(Math.min(image.startX + deltaX, maxX), minX);
  414. const newY = Math.max(Math.min(image.startY + deltaY, maxY), minY);
  415. gesture.imageWrapEl.style.transitionDuration = '0ms';
  416. gesture.imageWrapEl.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
  417. mousePanStart.x = e.clientX;
  418. mousePanStart.y = e.clientY;
  419. image.startX = newX;
  420. image.startY = newY;
  421. image.currentX = newX;
  422. image.currentY = newY;
  423. }
  424. function zoomIn(e) {
  425. const zoom = swiper.zoom;
  426. const params = swiper.params.zoom;
  427. if (!gesture.slideEl) {
  428. if (e && e.target) {
  429. gesture.slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`);
  430. }
  431. if (!gesture.slideEl) {
  432. if (swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual) {
  433. gesture.slideEl = elementChildren(swiper.slidesEl, `.${swiper.params.slideActiveClass}`)[0];
  434. } else {
  435. gesture.slideEl = swiper.slides[swiper.activeIndex];
  436. }
  437. }
  438. let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`);
  439. if (imageEl) {
  440. imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0];
  441. }
  442. gesture.imageEl = imageEl;
  443. if (imageEl) {
  444. gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0];
  445. } else {
  446. gesture.imageWrapEl = undefined;
  447. }
  448. }
  449. if (!gesture.imageEl || !gesture.imageWrapEl) return;
  450. if (swiper.params.cssMode) {
  451. swiper.wrapperEl.style.overflow = 'hidden';
  452. swiper.wrapperEl.style.touchAction = 'none';
  453. }
  454. gesture.slideEl.classList.add(`${params.zoomedSlideClass}`);
  455. let touchX;
  456. let touchY;
  457. let offsetX;
  458. let offsetY;
  459. let diffX;
  460. let diffY;
  461. let translateX;
  462. let translateY;
  463. let imageWidth;
  464. let imageHeight;
  465. let scaledWidth;
  466. let scaledHeight;
  467. let translateMinX;
  468. let translateMinY;
  469. let translateMaxX;
  470. let translateMaxY;
  471. let slideWidth;
  472. let slideHeight;
  473. if (typeof image.touchesStart.x === 'undefined' && e) {
  474. touchX = e.pageX;
  475. touchY = e.pageY;
  476. } else {
  477. touchX = image.touchesStart.x;
  478. touchY = image.touchesStart.y;
  479. }
  480. const prevScale = currentScale;
  481. const forceZoomRatio = typeof e === 'number' ? e : null;
  482. if (currentScale === 1 && forceZoomRatio) {
  483. touchX = undefined;
  484. touchY = undefined;
  485. image.touchesStart.x = undefined;
  486. image.touchesStart.y = undefined;
  487. }
  488. const maxRatio = getMaxRatio();
  489. zoom.scale = forceZoomRatio || maxRatio;
  490. currentScale = forceZoomRatio || maxRatio;
  491. if (e && !(currentScale === 1 && forceZoomRatio)) {
  492. slideWidth = gesture.slideEl.offsetWidth;
  493. slideHeight = gesture.slideEl.offsetHeight;
  494. offsetX = elementOffset(gesture.slideEl).left + window.scrollX;
  495. offsetY = elementOffset(gesture.slideEl).top + window.scrollY;
  496. diffX = offsetX + slideWidth / 2 - touchX;
  497. diffY = offsetY + slideHeight / 2 - touchY;
  498. imageWidth = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth;
  499. imageHeight = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight;
  500. scaledWidth = imageWidth * zoom.scale;
  501. scaledHeight = imageHeight * zoom.scale;
  502. translateMinX = Math.min(slideWidth / 2 - scaledWidth / 2, 0);
  503. translateMinY = Math.min(slideHeight / 2 - scaledHeight / 2, 0);
  504. translateMaxX = -translateMinX;
  505. translateMaxY = -translateMinY;
  506. if (prevScale > 0 && forceZoomRatio && typeof image.currentX === 'number' && typeof image.currentY === 'number') {
  507. translateX = image.currentX * zoom.scale / prevScale;
  508. translateY = image.currentY * zoom.scale / prevScale;
  509. } else {
  510. translateX = diffX * zoom.scale;
  511. translateY = diffY * zoom.scale;
  512. }
  513. if (translateX < translateMinX) {
  514. translateX = translateMinX;
  515. }
  516. if (translateX > translateMaxX) {
  517. translateX = translateMaxX;
  518. }
  519. if (translateY < translateMinY) {
  520. translateY = translateMinY;
  521. }
  522. if (translateY > translateMaxY) {
  523. translateY = translateMaxY;
  524. }
  525. } else {
  526. translateX = 0;
  527. translateY = 0;
  528. }
  529. if (forceZoomRatio && zoom.scale === 1) {
  530. gesture.originX = 0;
  531. gesture.originY = 0;
  532. }
  533. image.currentX = translateX;
  534. image.currentY = translateY;
  535. gesture.imageWrapEl.style.transitionDuration = '300ms';
  536. gesture.imageWrapEl.style.transform = `translate3d(${translateX}px, ${translateY}px,0)`;
  537. gesture.imageEl.style.transitionDuration = '300ms';
  538. gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`;
  539. }
  540. function zoomOut() {
  541. const zoom = swiper.zoom;
  542. const params = swiper.params.zoom;
  543. if (!gesture.slideEl) {
  544. if (swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual) {
  545. gesture.slideEl = elementChildren(swiper.slidesEl, `.${swiper.params.slideActiveClass}`)[0];
  546. } else {
  547. gesture.slideEl = swiper.slides[swiper.activeIndex];
  548. }
  549. let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`);
  550. if (imageEl) {
  551. imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0];
  552. }
  553. gesture.imageEl = imageEl;
  554. if (imageEl) {
  555. gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0];
  556. } else {
  557. gesture.imageWrapEl = undefined;
  558. }
  559. }
  560. if (!gesture.imageEl || !gesture.imageWrapEl) return;
  561. if (swiper.params.cssMode) {
  562. swiper.wrapperEl.style.overflow = '';
  563. swiper.wrapperEl.style.touchAction = '';
  564. }
  565. zoom.scale = 1;
  566. currentScale = 1;
  567. image.currentX = undefined;
  568. image.currentY = undefined;
  569. image.touchesStart.x = undefined;
  570. image.touchesStart.y = undefined;
  571. gesture.imageWrapEl.style.transitionDuration = '300ms';
  572. gesture.imageWrapEl.style.transform = 'translate3d(0,0,0)';
  573. gesture.imageEl.style.transitionDuration = '300ms';
  574. gesture.imageEl.style.transform = 'translate3d(0,0,0) scale(1)';
  575. gesture.slideEl.classList.remove(`${params.zoomedSlideClass}`);
  576. gesture.slideEl = undefined;
  577. gesture.originX = 0;
  578. gesture.originY = 0;
  579. if (swiper.params.zoom.panOnMouseMove) {
  580. mousePanStart = {
  581. x: 0,
  582. y: 0
  583. };
  584. if (isPanningWithMouse) {
  585. isPanningWithMouse = false;
  586. image.startX = 0;
  587. image.startY = 0;
  588. }
  589. }
  590. }
  591. // Toggle Zoom
  592. function zoomToggle(e) {
  593. const zoom = swiper.zoom;
  594. if (zoom.scale && zoom.scale !== 1) {
  595. // Zoom Out
  596. zoomOut();
  597. } else {
  598. // Zoom In
  599. zoomIn(e);
  600. }
  601. }
  602. function getListeners() {
  603. const passiveListener = swiper.params.passiveListeners ? {
  604. passive: true,
  605. capture: false
  606. } : false;
  607. const activeListenerWithCapture = swiper.params.passiveListeners ? {
  608. passive: false,
  609. capture: true
  610. } : true;
  611. return {
  612. passiveListener,
  613. activeListenerWithCapture
  614. };
  615. }
  616. // Attach/Detach Events
  617. function enable() {
  618. const zoom = swiper.zoom;
  619. if (zoom.enabled) return;
  620. zoom.enabled = true;
  621. const {
  622. passiveListener,
  623. activeListenerWithCapture
  624. } = getListeners();
  625. // Scale image
  626. swiper.wrapperEl.addEventListener('pointerdown', onGestureStart, passiveListener);
  627. swiper.wrapperEl.addEventListener('pointermove', onGestureChange, activeListenerWithCapture);
  628. ['pointerup', 'pointercancel', 'pointerout'].forEach(eventName => {
  629. swiper.wrapperEl.addEventListener(eventName, onGestureEnd, passiveListener);
  630. });
  631. // Move image
  632. swiper.wrapperEl.addEventListener('pointermove', onTouchMove, activeListenerWithCapture);
  633. }
  634. function disable() {
  635. const zoom = swiper.zoom;
  636. if (!zoom.enabled) return;
  637. zoom.enabled = false;
  638. const {
  639. passiveListener,
  640. activeListenerWithCapture
  641. } = getListeners();
  642. // Scale image
  643. swiper.wrapperEl.removeEventListener('pointerdown', onGestureStart, passiveListener);
  644. swiper.wrapperEl.removeEventListener('pointermove', onGestureChange, activeListenerWithCapture);
  645. ['pointerup', 'pointercancel', 'pointerout'].forEach(eventName => {
  646. swiper.wrapperEl.removeEventListener(eventName, onGestureEnd, passiveListener);
  647. });
  648. // Move image
  649. swiper.wrapperEl.removeEventListener('pointermove', onTouchMove, activeListenerWithCapture);
  650. }
  651. on('init', () => {
  652. if (swiper.params.zoom.enabled) {
  653. enable();
  654. }
  655. });
  656. on('destroy', () => {
  657. disable();
  658. });
  659. on('touchStart', (_s, e) => {
  660. if (!swiper.zoom.enabled) return;
  661. onTouchStart(e);
  662. });
  663. on('touchEnd', (_s, e) => {
  664. if (!swiper.zoom.enabled) return;
  665. onTouchEnd();
  666. });
  667. on('doubleTap', (_s, e) => {
  668. if (!swiper.animating && swiper.params.zoom.enabled && swiper.zoom.enabled && swiper.params.zoom.toggle) {
  669. zoomToggle(e);
  670. }
  671. });
  672. on('transitionEnd', () => {
  673. if (swiper.zoom.enabled && swiper.params.zoom.enabled) {
  674. onTransitionEnd();
  675. }
  676. });
  677. on('slideChange', () => {
  678. if (swiper.zoom.enabled && swiper.params.zoom.enabled && swiper.params.cssMode) {
  679. onTransitionEnd();
  680. }
  681. });
  682. Object.assign(swiper.zoom, {
  683. enable,
  684. disable,
  685. in: zoomIn,
  686. out: zoomOut,
  687. toggle: zoomToggle
  688. });
  689. }
  690. export { Zoom as default };