fix: zoom
This commit is contained in:
@@ -14,12 +14,14 @@ class ImageGallery extends HTMLElement {
|
|||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.startX = 0;
|
this.startX = 0;
|
||||||
this.startY = 0;
|
this.startY = 0;
|
||||||
this.lastTranslateX = 0;
|
|
||||||
this.lastTranslateY = 0;
|
|
||||||
|
|
||||||
// Pinch zoom state
|
// Pinch zoom state
|
||||||
this.initialPinchDistance = 0;
|
this.initialPinchDistance = 0;
|
||||||
this.initialZoom = 1;
|
this.initialZoom = 1;
|
||||||
|
this.pinchCenterX = 0;
|
||||||
|
this.pinchCenterY = 0;
|
||||||
|
this.initialTranslateX = 0;
|
||||||
|
this.initialTranslateY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -117,16 +119,56 @@ class ImageGallery extends HTMLElement {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
const delta = e.deltaY > 0 ? -0.15 : 0.15;
|
||||||
this.zoom(delta);
|
|
||||||
|
// Get mouse position relative to modal content center
|
||||||
|
const modalContent = this.shadowRoot.querySelector(".modal-content");
|
||||||
|
const rect = modalContent.getBoundingClientRect();
|
||||||
|
const mouseX = e.clientX - rect.left - rect.width / 2;
|
||||||
|
const mouseY = e.clientY - rect.top - rect.height / 2;
|
||||||
|
|
||||||
|
this.zoomToPoint(delta, mouseX, mouseY);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomToPoint(delta, pointX, pointY) {
|
||||||
|
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
||||||
|
if (!modalImage) return;
|
||||||
|
|
||||||
|
const prevZoom = this.currentZoom;
|
||||||
|
const newZoom = Math.max(1, Math.min(4, this.currentZoom + delta));
|
||||||
|
|
||||||
|
if (newZoom === prevZoom) return;
|
||||||
|
|
||||||
|
// Calculate new translate to zoom towards point
|
||||||
|
if (newZoom === 1) {
|
||||||
|
this.translateX = 0;
|
||||||
|
this.translateY = 0;
|
||||||
|
} else {
|
||||||
|
const zoomRatio = newZoom / prevZoom;
|
||||||
|
this.translateX = pointX - (pointX - this.translateX) * zoomRatio;
|
||||||
|
this.translateY = pointY - (pointY - this.translateY) * zoomRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentZoom = newZoom;
|
||||||
|
this.updateImageTransform(false);
|
||||||
|
modalImage.style.cursor = this.currentZoom > 1 ? "grab" : "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTouchStart(e) {
|
handleTouchStart(e) {
|
||||||
if (e.touches.length === 2) {
|
if (e.touches.length === 2) {
|
||||||
// Pinch zoom start
|
// Pinch zoom start
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
this.isDragging = false; // Stop any ongoing drag
|
||||||
this.initialPinchDistance = this.getPinchDistance(e.touches);
|
this.initialPinchDistance = this.getPinchDistance(e.touches);
|
||||||
this.initialZoom = this.currentZoom;
|
this.initialZoom = this.currentZoom;
|
||||||
|
this.initialTranslateX = this.translateX;
|
||||||
|
this.initialTranslateY = this.translateY;
|
||||||
|
|
||||||
|
// Get pinch center relative to viewport
|
||||||
|
const modalContent = this.shadowRoot.querySelector(".modal-content");
|
||||||
|
const rect = modalContent.getBoundingClientRect();
|
||||||
|
this.pinchCenterX = ((e.touches[0].clientX + e.touches[1].clientX) / 2) - rect.left - rect.width / 2;
|
||||||
|
this.pinchCenterY = ((e.touches[0].clientY + e.touches[1].clientY) / 2) - rect.top - rect.height / 2;
|
||||||
} else if (e.touches.length === 1 && this.currentZoom > 1) {
|
} else if (e.touches.length === 1 && this.currentZoom > 1) {
|
||||||
// Single touch drag when zoomed
|
// Single touch drag when zoomed
|
||||||
this.startDrag(e);
|
this.startDrag(e);
|
||||||
@@ -134,21 +176,27 @@ class ImageGallery extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTouchMove(e) {
|
handleTouchMove(e) {
|
||||||
if (e.touches.length === 2) {
|
if (e.touches.length === 2 && this.initialPinchDistance > 0) {
|
||||||
// Pinch zoom
|
// Pinch zoom with zoom-to-point
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const currentDistance = this.getPinchDistance(e.touches);
|
const currentDistance = this.getPinchDistance(e.touches);
|
||||||
const scale = currentDistance / this.initialPinchDistance;
|
const scale = currentDistance / this.initialPinchDistance;
|
||||||
const newZoom = Math.max(1, Math.min(4, this.initialZoom * scale));
|
const newZoom = Math.max(1, Math.min(4, this.initialZoom * scale));
|
||||||
|
const prevZoom = this.currentZoom;
|
||||||
|
|
||||||
this.currentZoom = newZoom;
|
this.currentZoom = newZoom;
|
||||||
|
|
||||||
if (this.currentZoom === 1) {
|
if (this.currentZoom === 1) {
|
||||||
this.translateX = 0;
|
this.translateX = 0;
|
||||||
this.translateY = 0;
|
this.translateY = 0;
|
||||||
|
} else {
|
||||||
|
// Adjust translate to zoom towards pinch center
|
||||||
|
const zoomRatio = this.currentZoom / this.initialZoom;
|
||||||
|
this.translateX = this.pinchCenterX - (this.pinchCenterX - this.initialTranslateX) * zoomRatio;
|
||||||
|
this.translateY = this.pinchCenterY - (this.pinchCenterY - this.initialTranslateY) * zoomRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateImageTransform();
|
this.updateImageTransform(false); // No transition during pinch
|
||||||
|
|
||||||
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
||||||
if (modalImage) {
|
if (modalImage) {
|
||||||
@@ -209,7 +257,12 @@ class ImageGallery extends HTMLElement {
|
|||||||
this.translateX = clientX - this.startX;
|
this.translateX = clientX - this.startX;
|
||||||
this.translateY = clientY - this.startY;
|
this.translateY = clientY - this.startY;
|
||||||
|
|
||||||
this.updateImageTransform();
|
// Use no transition during drag for smooth movement
|
||||||
|
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
||||||
|
if (modalImage) {
|
||||||
|
modalImage.style.transition = "none";
|
||||||
|
modalImage.style.transform = `scale(${this.currentZoom}) translate(${this.translateX / this.currentZoom}px, ${this.translateY / this.currentZoom}px)`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
endDrag() {
|
endDrag() {
|
||||||
@@ -224,10 +277,19 @@ class ImageGallery extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateImageTransform() {
|
updateImageTransform(useTransition = true) {
|
||||||
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
||||||
if (modalImage) {
|
if (modalImage) {
|
||||||
|
if (!useTransition) {
|
||||||
|
modalImage.style.transition = "none";
|
||||||
|
}
|
||||||
modalImage.style.transform = `scale(${this.currentZoom}) translate(${this.translateX / this.currentZoom}px, ${this.translateY / this.currentZoom}px)`;
|
modalImage.style.transform = `scale(${this.currentZoom}) translate(${this.translateX / this.currentZoom}px, ${this.translateY / this.currentZoom}px)`;
|
||||||
|
|
||||||
|
// Force reflow and restore transition
|
||||||
|
if (!useTransition) {
|
||||||
|
modalImage.offsetHeight; // Force reflow
|
||||||
|
modalImage.style.transition = "transform 0.2s ease";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,13 +300,25 @@ class ImageGallery extends HTMLElement {
|
|||||||
if (modal && modalImage && this.images[index]) {
|
if (modal && modalImage && this.images[index]) {
|
||||||
modalImage.src = this.images[index];
|
modalImage.src = this.images[index];
|
||||||
|
|
||||||
// Reset zoom and pan
|
// Reset all state
|
||||||
this.currentZoom = 1;
|
this.currentZoom = 1;
|
||||||
this.translateX = 0;
|
this.translateX = 0;
|
||||||
this.translateY = 0;
|
this.translateY = 0;
|
||||||
this.updateImageTransform();
|
this.isDragging = false;
|
||||||
|
this.initialPinchDistance = 0;
|
||||||
|
this.initialZoom = 1;
|
||||||
|
this.initialTranslateX = 0;
|
||||||
|
this.initialTranslateY = 0;
|
||||||
|
|
||||||
|
// Reset transform without transition
|
||||||
|
modalImage.style.transition = "none";
|
||||||
|
modalImage.style.transform = "scale(1) translate(0px, 0px)";
|
||||||
modalImage.style.cursor = "default";
|
modalImage.style.cursor = "default";
|
||||||
|
|
||||||
|
// Force reflow then restore transition
|
||||||
|
modalImage.offsetHeight;
|
||||||
|
modalImage.style.transition = "transform 0.2s ease";
|
||||||
|
|
||||||
modal.classList.add("open");
|
modal.classList.add("open");
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
}
|
}
|
||||||
@@ -278,10 +352,19 @@ class ImageGallery extends HTMLElement {
|
|||||||
resetZoom() {
|
resetZoom() {
|
||||||
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
const modalImage = this.shadowRoot.querySelector(".modal-image");
|
||||||
if (modalImage) {
|
if (modalImage) {
|
||||||
|
// Reset all state
|
||||||
this.currentZoom = 1;
|
this.currentZoom = 1;
|
||||||
this.translateX = 0;
|
this.translateX = 0;
|
||||||
this.translateY = 0;
|
this.translateY = 0;
|
||||||
this.updateImageTransform();
|
this.isDragging = false;
|
||||||
|
this.initialPinchDistance = 0;
|
||||||
|
this.initialZoom = 1;
|
||||||
|
this.initialTranslateX = 0;
|
||||||
|
this.initialTranslateY = 0;
|
||||||
|
|
||||||
|
// Ensure transition is enabled for smooth reset animation
|
||||||
|
modalImage.style.transition = "transform 0.3s ease";
|
||||||
|
modalImage.style.transform = "scale(1) translate(0px, 0px)";
|
||||||
modalImage.style.cursor = "default";
|
modalImage.style.cursor = "default";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user