From 939a4e9c57d7e322a63ae41522ef892a1b089733 Mon Sep 17 00:00:00 2001 From: Tim Rijkse Date: Fri, 16 Jan 2026 09:52:16 +0100 Subject: [PATCH] fix: zoom --- js/components/image-gallery.js | 85 +++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/js/components/image-gallery.js b/js/components/image-gallery.js index 4b7fbd5..21403ce 100644 --- a/js/components/image-gallery.js +++ b/js/components/image-gallery.js @@ -16,6 +16,10 @@ class ImageGallery extends HTMLElement { this.startY = 0; this.lastTranslateX = 0; this.lastTranslateY = 0; + + // Pinch zoom state + this.initialPinchDistance = 0; + this.initialZoom = 1; } connectedCallback() { @@ -90,7 +94,81 @@ class ImageGallery extends HTMLElement { // Prevent default drag behavior modalImage.addEventListener("dragstart", (e) => e.preventDefault()); + + // Mouse wheel zoom + modalImage.addEventListener("wheel", (e) => this.handleWheel(e), { passive: false }); } + + // Pinch zoom for touch devices on modal content + const modalContent = this.shadowRoot.querySelector(".modal-content"); + if (modalContent) { + modalContent.addEventListener("touchstart", (e) => this.handleTouchStart(e), { passive: false }); + modalContent.addEventListener("touchmove", (e) => this.handleTouchMove(e), { passive: false }); + modalContent.addEventListener("touchend", (e) => this.handleTouchEnd(e)); + + // Prevent default browser gestures on the modal + modalContent.addEventListener("gesturestart", (e) => e.preventDefault()); + modalContent.addEventListener("gesturechange", (e) => e.preventDefault()); + modalContent.addEventListener("gestureend", (e) => e.preventDefault()); + } + } + + handleWheel(e) { + e.preventDefault(); + e.stopPropagation(); + + const delta = e.deltaY > 0 ? -0.1 : 0.1; + this.zoom(delta); + } + + handleTouchStart(e) { + if (e.touches.length === 2) { + // Pinch zoom start + e.preventDefault(); + this.initialPinchDistance = this.getPinchDistance(e.touches); + this.initialZoom = this.currentZoom; + } else if (e.touches.length === 1 && this.currentZoom > 1) { + // Single touch drag when zoomed + this.startDrag(e); + } + } + + handleTouchMove(e) { + if (e.touches.length === 2) { + // Pinch zoom + e.preventDefault(); + const currentDistance = this.getPinchDistance(e.touches); + const scale = currentDistance / this.initialPinchDistance; + const newZoom = Math.max(1, Math.min(4, this.initialZoom * scale)); + + this.currentZoom = newZoom; + + if (this.currentZoom === 1) { + this.translateX = 0; + this.translateY = 0; + } + + this.updateImageTransform(); + + const modalImage = this.shadowRoot.querySelector(".modal-image"); + if (modalImage) { + modalImage.style.cursor = this.currentZoom > 1 ? "grab" : "default"; + } + } else if (e.touches.length === 1 && this.isDragging) { + // Single touch drag + this.drag(e); + } + } + + handleTouchEnd(e) { + this.initialPinchDistance = 0; + this.endDrag(); + } + + getPinchDistance(touches) { + const dx = touches[0].clientX - touches[1].clientX; + const dy = touches[0].clientY - touches[1].clientY; + return Math.sqrt(dx * dx + dy * dy); } startDrag(e) { @@ -263,6 +341,8 @@ class ImageGallery extends HTMLElement { background-color: rgba(0, 0, 0, 0.95); z-index: 1000; flex-direction: column; + touch-action: none; + -webkit-touch-callout: none; } .modal-overlay.open { @@ -321,11 +401,13 @@ class ImageGallery extends HTMLElement { .modal-content { flex: 1; - overflow: auto; + overflow: hidden; display: flex; align-items: center; justify-content: center; padding: var(--spacing-md, 1rem); + touch-action: none; + -webkit-touch-callout: none; } .modal-image { @@ -336,6 +418,7 @@ class ImageGallery extends HTMLElement { cursor: default; user-select: none; -webkit-user-drag: none; + touch-action: none; }