gallery pinch zoom
This commit is contained in:
parent
37b0244517
commit
5df7b5915f
@ -65,16 +65,24 @@ const locale = Astro.currentLocale || "en";
|
||||
images: ${JSON.stringify(images)},
|
||||
lastTapTime: 0,
|
||||
doubleTapDelay: 300,
|
||||
scale: 1,
|
||||
panX: 0,
|
||||
panY: 0,
|
||||
isPanning: false,
|
||||
startPanX: 0,
|
||||
startPanY: 0,
|
||||
handleSwipe() {
|
||||
if (!this.isSwiping) return;
|
||||
if (!this.isSwiping || this.scale > 1) return; // Disable swipe when zoomed
|
||||
const swipeDistance = this.touchEndX - this.touchStartX;
|
||||
if (Math.abs(swipeDistance) >= this.minSwipeDistance) {
|
||||
if (swipeDistance > 0 && this.currentIndex > 0) {
|
||||
// Swiped right, show previous image
|
||||
this.currentIndex--;
|
||||
this.resetZoom();
|
||||
} else if (swipeDistance < 0 && this.currentIndex < this.total - 1) {
|
||||
// Swiped left, show next image
|
||||
this.currentIndex++;
|
||||
this.resetZoom();
|
||||
}
|
||||
}
|
||||
this.isSwiping = false;
|
||||
@ -82,6 +90,7 @@ const locale = Astro.currentLocale || "en";
|
||||
preloadAndOpen() {
|
||||
if (this.isSwiping) return;
|
||||
this.lightboxLoaded = false;
|
||||
this.resetZoom();
|
||||
let img = new Image();
|
||||
img.src = this.images[this.currentIndex].src;
|
||||
img.onload = () => {
|
||||
@ -91,11 +100,12 @@ const locale = Astro.currentLocale || "en";
|
||||
},
|
||||
preloadImage(index) {
|
||||
// Preload without affecting lightboxLoaded state (for navigation within lightbox)
|
||||
this.resetZoom();
|
||||
let img = new Image();
|
||||
img.src = this.images[index].src;
|
||||
// No need to wait for load when navigating within lightbox
|
||||
},
|
||||
handleThumbnailClick(index) {
|
||||
handleImageInteraction(index) {
|
||||
const currentTime = Date.now();
|
||||
const timeDiff = currentTime - this.lastTapTime;
|
||||
|
||||
@ -109,6 +119,46 @@ const locale = Astro.currentLocale || "en";
|
||||
}
|
||||
|
||||
this.lastTapTime = currentTime;
|
||||
},
|
||||
resetZoom() {
|
||||
this.scale = 1;
|
||||
this.panX = 0;
|
||||
this.panY = 0;
|
||||
this.isPanning = false;
|
||||
},
|
||||
handleWheel(e) {
|
||||
e.preventDefault();
|
||||
const zoomSpeed = 0.1;
|
||||
const newScale = this.scale - Math.sign(e.deltaY) * zoomSpeed;
|
||||
this.scale = Math.min(Math.max(1, newScale), 5); // Limit zoom between 1x and 5x
|
||||
|
||||
if (this.scale === 1) {
|
||||
this.panX = 0;
|
||||
this.panY = 0;
|
||||
}
|
||||
},
|
||||
handlePanStart(e) {
|
||||
if (this.scale <= 1) return;
|
||||
this.isPanning = true;
|
||||
this.startPanX = e.clientX - this.panX;
|
||||
this.startPanY = e.clientY - this.panY;
|
||||
e.preventDefault();
|
||||
},
|
||||
handlePanMove(e) {
|
||||
if (!this.isPanning) return;
|
||||
e.preventDefault();
|
||||
this.panX = e.clientX - this.startPanX;
|
||||
this.panY = e.clientY - this.startPanY;
|
||||
},
|
||||
handlePanEnd(e) {
|
||||
this.isPanning = false;
|
||||
},
|
||||
toggleZoom(e) {
|
||||
if (this.scale > 1) {
|
||||
this.resetZoom();
|
||||
} else {
|
||||
this.scale = 2; // Zoom to 2x on double click
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
@ -125,8 +175,8 @@ const locale = Astro.currentLocale || "en";
|
||||
class="product-gallery bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"><div class="flex flex-col h-full p-4">
|
||||
<!-- Main Image (with swipe functionality) -->
|
||||
<div
|
||||
class="flex-1 flex items-center justify-center cursor-pointer rounded-lg"
|
||||
@click="preloadAndOpen()"
|
||||
class="flex-1 flex items-center justify-center cursor-pointer rounded-lg touch-manipulation"
|
||||
@click="handleImageInteraction(currentIndex)"
|
||||
@touchstart="touchStartX = $event.touches[0].clientX; isSwiping = true;"
|
||||
@touchend="touchEndX = $event.changedTouches[0].clientX; handleSwipe();"
|
||||
@touchcancel="isSwiping = false;"
|
||||
@ -140,7 +190,6 @@ const locale = Astro.currentLocale || "en";
|
||||
format="avif"
|
||||
placeholder="blurred"
|
||||
sizes={mergedGallerySettings.SIZES_REGULAR}
|
||||
sizes={mergedGallerySettings.SIZES_REGULAR}
|
||||
s={s || image.hash}
|
||||
attributes={{
|
||||
img: { class: "main-image p-4 rounded-lg max-h-[60vh] aspect-square" }
|
||||
@ -166,9 +215,9 @@ const locale = Astro.currentLocale || "en";
|
||||
{images.map((image, index) => (
|
||||
<button
|
||||
key={index}
|
||||
x-on:click={`handleThumbnailClick(${index})`}
|
||||
x-on:click={`handleImageInteraction(${index})`}
|
||||
:class={`currentIndex === ${index} ? 'ring-2 ring-orange-500' : ''`}
|
||||
class="thumbnail thumbnail-btn rounded-lg"
|
||||
class="thumbnail thumbnail-btn rounded-lg touch-manipulation"
|
||||
>
|
||||
<Img
|
||||
src={image.src}
|
||||
@ -199,31 +248,44 @@ const locale = Astro.currentLocale || "en";
|
||||
class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center lightbox z-50"
|
||||
>
|
||||
<div
|
||||
class="relative max-w-full max-h-full"
|
||||
class="relative max-w-full max-h-full transition-transform duration-100 ease-out"
|
||||
@touchstart="touchStartX = $event.touches[0].clientX; isSwiping = true;"
|
||||
@touchend="touchEndX = $event.changedTouches[0].clientX; handleSwipe();"
|
||||
@touchcancel="isSwiping = false;"
|
||||
@wheel="handleWheel($event)"
|
||||
@mousedown="handlePanStart($event)"
|
||||
@mousemove.window="handlePanMove($event)"
|
||||
@mouseup.window="handlePanEnd($event)"
|
||||
@dblclick="toggleZoom($event)"
|
||||
>
|
||||
{images.map((image, index) => (
|
||||
<div x-show={`currentIndex === ${index}`} key={index}>
|
||||
<Img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
placeholder="blurred"
|
||||
format="avif"
|
||||
objectFit="contain"
|
||||
sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
|
||||
sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
|
||||
s={s || image.hash}
|
||||
attributes={{
|
||||
img: { class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg lightbox-main" }
|
||||
}}
|
||||
/>
|
||||
{ ((mergedLightboxSettings.SHOW_TITLE && image.title && image.title.trim().length > 0) || (mergedLightboxSettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0)) && (
|
||||
<div class="absolute bottom-0 left-1/2 transform -translate-x-1/2 m-[8px] max-h-[32vh] p-2 text-white bg-black/50 rounded-lg" style="width: 90%;">
|
||||
{ mergedLightboxSettings.SHOW_TITLE && image.title && image.title.trim().length > 0 && ( <h3 class="text-xl"><Translate>{image.title}</Translate></h3>)}
|
||||
{ mergedLightboxSettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0 && (<p><Translate>{image.description}</Translate></p>)} </div>
|
||||
)}
|
||||
<div
|
||||
x-show={`currentIndex === ${index}`}
|
||||
key={index}
|
||||
class="w-full h-full flex items-center justify-center p-4"
|
||||
>
|
||||
<div
|
||||
:style="'transform: translate(' + panX + 'px, ' + panY + 'px) scale(' + scale + '); cursor: ' + (scale > 1 ? 'grab' : 'default') + ';'"
|
||||
class="transition-transform duration-100 ease-out flex items-center justify-center max-w-full max-h-full"
|
||||
>
|
||||
<Img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
placeholder="blurred"
|
||||
format="avif"
|
||||
objectFit="contain"
|
||||
sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
|
||||
s={s || image.hash}
|
||||
attributes={{
|
||||
img: { class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg lightbox-main select-none" }
|
||||
}}
|
||||
/>
|
||||
{ ((mergedLightboxSettings.SHOW_TITLE && image.title && image.title.trim().length > 0) || (mergedLightboxSettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0)) && (
|
||||
<div class="absolute bottom-0 left-1/2 transform -translate-x-1/2 m-[8px] max-h-[32vh] p-2 text-white bg-black/50 rounded-lg" style="width: 90%;">
|
||||
{ mergedLightboxSettings.SHOW_TITLE && image.title && image.title.trim().length > 0 && ( <h3 class="text-xl"><Translate>{image.title}</Translate></h3>)}
|
||||
{ mergedLightboxSettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0 && (<p><Translate>{image.description}</Translate></p>)} </div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<!-- Close Button -->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user