From c4619bb01dd929b29397afc7697958ca23c82c90 Mon Sep 17 00:00:00 2001 From: Derek Brooks Date: Thu, 2 Jan 2025 11:49:53 -0600 Subject: [PATCH] feat(zoom): Add ability to pan around an image on mouse move (#7831) * Add ability to pan around an image by mouse to close #7306 * Make the zoom module's panWithMouse param more descriptive * reuse onTouchMove event --------- Co-authored-by: Vladimir Kharlampidi --- src/modules/zoom/zoom.mjs | 69 +++++++++++++++++++++++++++++++++++++ src/types/modules/zoom.d.ts | 6 ++++ 2 files changed, 75 insertions(+) diff --git a/src/modules/zoom/zoom.mjs b/src/modules/zoom/zoom.mjs index ba5860821..2279b5c92 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, + panOnMouseMove: 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; @@ -262,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; } @@ -270,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; @@ -435,6 +448,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 (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 +658,15 @@ export default function Zoom({ swiper, extendParams, on, emit }) { gesture.slideEl = undefined; gesture.originX = 0; gesture.originY = 0; + + if (swiper.params.zoom.panOnMouseMove) { + mousePanStart = { x: 0, y: 0 }; + if (isPanningWithMouse) { + isPanningWithMouse = false; + image.startX = 0; + image.startY = 0; + } + } } // Toggle Zoom diff --git a/src/types/modules/zoom.d.ts b/src/types/modules/zoom.d.ts index 658f5baef..f7b9dde44 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 over the image + * + * @default false + */ + panOnMouseMove?: boolean; /** * Enable/disable zoom-in by slide's double tap *