From 4c0d13e59a2ee740fde20971e7b3189b59e71f6e Mon Sep 17 00:00:00 2001 From: Derek Brooks Date: Sun, 22 Dec 2024 21:00:24 -0600 Subject: [PATCH 1/3] Add ability to pan around an image by mouse to close #7306 --- src/modules/zoom/zoom.mjs | 68 +++++++++++++++++++++++++++++++++++++ src/types/modules/zoom.d.ts | 6 ++++ 2 files changed, 74 insertions(+) diff --git a/src/modules/zoom/zoom.mjs b/src/modules/zoom/zoom.mjs index ba5860821..a6c916be0 100644 --- a/src/modules/zoom/zoom.mjs +++ b/src/modules/zoom/zoom.mjs @@ -14,6 +14,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { limitToOriginalSize: false, maxRatio: 3, minRatio: 1, + panWithMouse: false, toggle: true, containerClass: 'swiper-zoom-container', zoomedSlideClass: 'swiper-slide-zoomed', @@ -26,6 +27,9 @@ export default function Zoom({ swiper, extendParams, on, emit }) { let currentScale = 1; let isScaling = false; + let isPanningWithMouse = false; + let mousePanStart = { x: 0, y: 0 }; + const mousePanSensitivity = -3; // Negative to invert pan direction let fakeGestureTouched; let fakeGestureMoved; let preventZoomOut; @@ -435,6 +439,53 @@ export default function Zoom({ swiper, extendParams, on, emit }) { gesture.originY = 0; } } + function onMouseMove(e) { + // Only pan if zoomed in and mouse panning is enabled + if (!swiper.params.zoom.panWithMouse || currentScale <= 1 || !gesture.imageWrapEl) return; + if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) return; + + const currentTransform = window.getComputedStyle(gesture.imageWrapEl).transform; + const matrix = new window.DOMMatrix(currentTransform); + + if (!isPanningWithMouse) { + isPanningWithMouse = true; + mousePanStart.x = e.clientX; + mousePanStart.y = e.clientY; + + image.startX = matrix.e; + image.startY = matrix.f; + image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth; + image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight; + + gesture.slideWidth = gesture.slideEl.offsetWidth; + gesture.slideHeight = gesture.slideEl.offsetHeight; + return; + } + + const deltaX = (e.clientX - mousePanStart.x) * mousePanSensitivity; + const deltaY = (e.clientY - mousePanStart.y) * mousePanSensitivity; + + const scaledWidth = image.width * currentScale; + const scaledHeight = image.height * currentScale; + const slideWidth = gesture.slideWidth; + const slideHeight = gesture.slideHeight; + + const minX = Math.min(slideWidth / 2 - scaledWidth / 2, 0); + const maxX = -minX; + const minY = Math.min(slideHeight / 2 - scaledHeight / 2, 0); + const maxY = -minY; + + const newX = Math.max(Math.min(image.startX + deltaX, maxX), minX); + const newY = Math.max(Math.min(image.startY + deltaY, maxY), minY); + + gesture.imageWrapEl.style.transitionDuration = '0ms'; + gesture.imageWrapEl.style.transform = `translate3d(${newX}px, ${newY}px, 0)`; + + mousePanStart.x = e.clientX; + mousePanStart.y = e.clientY; + image.startX = newX; + image.startY = newY; + } function zoomIn(e) { const zoom = swiper.zoom; @@ -598,6 +649,13 @@ export default function Zoom({ swiper, extendParams, on, emit }) { gesture.slideEl = undefined; gesture.originX = 0; gesture.originY = 0; + + if (swiper.params.zoom.panWithMouse) { + isPanningWithMouse = false; + mousePanStart = { x: 0, y: 0 }; + image.startX = 0; + image.startY = 0; + } } // Toggle Zoom @@ -639,6 +697,11 @@ export default function Zoom({ swiper, extendParams, on, emit }) { // Move image swiper.wrapperEl.addEventListener('pointermove', onTouchMove, activeListenerWithCapture); + + // Mouse panning + if (swiper.params.zoom.panWithMouse) { + swiper.wrapperEl.addEventListener('mousemove', onMouseMove, activeListenerWithCapture); + } } function disable() { const zoom = swiper.zoom; @@ -656,6 +719,11 @@ export default function Zoom({ swiper, extendParams, on, emit }) { // Move image swiper.wrapperEl.removeEventListener('pointermove', onTouchMove, activeListenerWithCapture); + + // Mouse panning + if (swiper.params.zoom.panWithMouse) { + swiper.wrapperEl.removeEventListener('mousemove', onMouseMove, activeListenerWithCapture); + } } on('init', () => { diff --git a/src/types/modules/zoom.d.ts b/src/types/modules/zoom.d.ts index 658f5baef..0c75498c0 100644 --- a/src/types/modules/zoom.d.ts +++ b/src/types/modules/zoom.d.ts @@ -63,6 +63,12 @@ export interface ZoomOptions { * @default 1 */ minRatio?: number; + /** + * When set to true, a zoomed in image will automatically pan while moving the mouse + * + * @default false + */ + panWithMouse?: boolean; /** * Enable/disable zoom-in by slide's double tap * From b8d9adf519fee6767d29dd8baaefe3b88125cd44 Mon Sep 17 00:00:00 2001 From: Derek Brooks Date: Mon, 23 Dec 2024 08:27:59 -0600 Subject: [PATCH 2/3] Make the zoom module's panWithMouse param more descriptive --- src/modules/zoom/zoom.mjs | 10 +++++----- src/types/modules/zoom.d.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/zoom/zoom.mjs b/src/modules/zoom/zoom.mjs index a6c916be0..a10175b87 100644 --- a/src/modules/zoom/zoom.mjs +++ b/src/modules/zoom/zoom.mjs @@ -14,7 +14,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { limitToOriginalSize: false, maxRatio: 3, minRatio: 1, - panWithMouse: false, + panOnMouseMove: false, toggle: true, containerClass: 'swiper-zoom-container', zoomedSlideClass: 'swiper-slide-zoomed', @@ -441,7 +441,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { } function onMouseMove(e) { // Only pan if zoomed in and mouse panning is enabled - if (!swiper.params.zoom.panWithMouse || currentScale <= 1 || !gesture.imageWrapEl) return; + if (!swiper.params.zoom.panOnMouseMove || currentScale <= 1 || !gesture.imageWrapEl) return; if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) return; const currentTransform = window.getComputedStyle(gesture.imageWrapEl).transform; @@ -650,7 +650,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { gesture.originX = 0; gesture.originY = 0; - if (swiper.params.zoom.panWithMouse) { + if (swiper.params.zoom.panOnMouseMove) { isPanningWithMouse = false; mousePanStart = { x: 0, y: 0 }; image.startX = 0; @@ -699,7 +699,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { swiper.wrapperEl.addEventListener('pointermove', onTouchMove, activeListenerWithCapture); // Mouse panning - if (swiper.params.zoom.panWithMouse) { + if (swiper.params.zoom.panOnMouseMove) { swiper.wrapperEl.addEventListener('mousemove', onMouseMove, activeListenerWithCapture); } } @@ -721,7 +721,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { swiper.wrapperEl.removeEventListener('pointermove', onTouchMove, activeListenerWithCapture); // Mouse panning - if (swiper.params.zoom.panWithMouse) { + if (swiper.params.zoom.panOnMouseMove) { swiper.wrapperEl.removeEventListener('mousemove', onMouseMove, activeListenerWithCapture); } } diff --git a/src/types/modules/zoom.d.ts b/src/types/modules/zoom.d.ts index 0c75498c0..f7b9dde44 100644 --- a/src/types/modules/zoom.d.ts +++ b/src/types/modules/zoom.d.ts @@ -64,11 +64,11 @@ export interface ZoomOptions { */ minRatio?: number; /** - * When set to true, a zoomed in image will automatically pan while moving the mouse + * When set to true, a zoomed in image will automatically pan while moving the mouse over the image * * @default false */ - panWithMouse?: boolean; + panOnMouseMove?: boolean; /** * Enable/disable zoom-in by slide's double tap * From c4a164c4993850c52b50da8dae719754643e397a Mon Sep 17 00:00:00 2001 From: Vladimir Kharlampidi Date: Thu, 2 Jan 2025 12:48:36 -0500 Subject: [PATCH 3/3] reuse onTouchMove event --- src/modules/zoom/zoom.mjs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/modules/zoom/zoom.mjs b/src/modules/zoom/zoom.mjs index a10175b87..2279b5c92 100644 --- a/src/modules/zoom/zoom.mjs +++ b/src/modules/zoom/zoom.mjs @@ -266,6 +266,9 @@ export default function Zoom({ swiper, extendParams, on, emit }) { image.touchesStart.y = event.pageY; } function onTouchMove(e) { + const isMouseEvent = e.pointerType === 'mouse'; + const isMousePan = isMouseEvent && swiper.params.zoom.panOnMouseMove; + if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) { return; } @@ -274,8 +277,14 @@ export default function Zoom({ swiper, extendParams, on, emit }) { return; } if (!image.isTouched || !gesture.slideEl) { + if (isMousePan) onMouseMove(e); + return; + } + if (isMousePan) { + onMouseMove(e); return; } + if (!image.isMoved) { image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth; image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight; @@ -441,7 +450,7 @@ export default function Zoom({ swiper, extendParams, on, emit }) { } function onMouseMove(e) { // Only pan if zoomed in and mouse panning is enabled - if (!swiper.params.zoom.panOnMouseMove || currentScale <= 1 || !gesture.imageWrapEl) return; + if (currentScale <= 1 || !gesture.imageWrapEl) return; if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) return; const currentTransform = window.getComputedStyle(gesture.imageWrapEl).transform; @@ -651,10 +660,12 @@ export default function Zoom({ swiper, extendParams, on, emit }) { gesture.originY = 0; if (swiper.params.zoom.panOnMouseMove) { - isPanningWithMouse = false; mousePanStart = { x: 0, y: 0 }; - image.startX = 0; - image.startY = 0; + if (isPanningWithMouse) { + isPanningWithMouse = false; + image.startX = 0; + image.startY = 0; + } } } @@ -697,11 +708,6 @@ export default function Zoom({ swiper, extendParams, on, emit }) { // Move image swiper.wrapperEl.addEventListener('pointermove', onTouchMove, activeListenerWithCapture); - - // Mouse panning - if (swiper.params.zoom.panOnMouseMove) { - swiper.wrapperEl.addEventListener('mousemove', onMouseMove, activeListenerWithCapture); - } } function disable() { const zoom = swiper.zoom; @@ -719,11 +725,6 @@ export default function Zoom({ swiper, extendParams, on, emit }) { // Move image swiper.wrapperEl.removeEventListener('pointermove', onTouchMove, activeListenerWithCapture); - - // Mouse panning - if (swiper.params.zoom.panOnMouseMove) { - swiper.wrapperEl.removeEventListener('mousemove', onMouseMove, activeListenerWithCapture); - } } on('init', () => {