From e7bc3821d1596e9c638b9cd2a644b9742473f9eb Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:25:08 +0900 Subject: [PATCH] Update MkImageGallery.item.vue --- .../src/components/MkImageGallery.item.vue | 108 +++++++++++++----- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/packages/frontend/src/components/MkImageGallery.item.vue b/packages/frontend/src/components/MkImageGallery.item.vue index 7866d4ef32..41c0498ff7 100644 --- a/packages/frontend/src/components/MkImageGallery.item.vue +++ b/packages/frontend/src/components/MkImageGallery.item.vue @@ -81,14 +81,42 @@ const defaultTranslation = calcDefaultTranslation(props.image); const size = ref({ width: defaultSize.width, height: defaultSize.height }); const translation = ref({ x: defaultTranslation.x, y: defaultTranslation.y }); +let isAnimating = false; + +function beginAnimation(to: { width: number; height: number; x: number; y: number }, duration: number) { + const easing = (t: number) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; // easeInOutQuad + + const startTime = performance.now(); + const startSize = { ...size.value }; + const startTranslation = { ...translation.value }; + isAnimating = true; + + function animate() { + const now = performance.now(); + const elapsed = now - startTime; + const t = easing(Math.min(elapsed / duration, 1)); + + size.value.width = startSize.width + (to.width - startSize.width) * t; + size.value.height = startSize.height + (to.height - startSize.height) * t; + translation.value.x = startTranslation.x + (to.x - startTranslation.x) * t; + translation.value.y = startTranslation.y + (to.y - startTranslation.y) * t; + + if (t < 1) { + window.requestAnimationFrame(animate); + } else { + isAnimating = false; + } + } + + window.requestAnimationFrame(animate); +} + const isZooming = ref(false); -function zoomInTo(x: number, y: number, factor = 1.1) { +function zoomInTo(x: number, y: number, factor = 1.1, withAnimation = false) { const newWidth = size.value.width * factor; const newHeight = size.value.height * factor; - - size.value.width = newWidth; - size.value.height = newHeight; + isZooming.value = true; // Center the image on the cursor const rect = imageEl.value?.getBoundingClientRect(); @@ -97,9 +125,19 @@ function zoomInTo(x: number, y: number, factor = 1.1) { const offsetX = x - rect.left; const offsetY = y - rect.top; - translation.value.x -= offsetX * (factor - 1); - translation.value.y -= offsetY * (factor - 1); - isZooming.value = true; + if (withAnimation) { + beginAnimation({ + width: newWidth, + height: newHeight, + x: translation.value.x - offsetX * (factor - 1), + y: translation.value.y - offsetY * (factor - 1), + }, 300); + } else { + size.value.width = newWidth; + size.value.height = newHeight; + translation.value.x -= offsetX * (factor - 1); + translation.value.y -= offsetY * (factor - 1); + } } function onWheel(event: WheelEvent) { @@ -130,13 +168,14 @@ function onZoomGesture(ev: { delta: number; centerX: number; centerY: number }) } function onZoomGestureEnd() { - // TODO: animation if (size.value.width < defaultSize.width || size.value.height < defaultSize.height) { - size.value.width = defaultSize.width; - size.value.height = defaultSize.height; - translation.value.x = defaultTranslation.x; - translation.value.y = defaultTranslation.y; isZooming.value = false; + beginAnimation({ + width: defaultSize.width, + height: defaultSize.height, + x: defaultTranslation.x, + y: defaultTranslation.y, + }, 300); } } @@ -144,6 +183,7 @@ function onZoomGestureEnd() { let isDragging = false; let lastX = 0; let lastY = 0; +let currentPointerId: number | null = null; const pointerEventCache = new Map(); let pointerVec = { x: 0, y: 0 }; @@ -156,6 +196,9 @@ function onPointerdown(ev: PointerEvent) { lastX = ev.clientX; lastY = ev.clientY; pointerVec = { x: 0, y: 0 }; + if (currentPointerId == null) { + currentPointerId = ev.pointerId; + } } let prevTwoTouchPointsDistance = 0; @@ -171,6 +214,8 @@ function onPointermove(ev: PointerEvent) { pointerEventCache.set(ev.pointerId, ev); if (pointerEventCache.size > 1) { // 2本指での操作 + pointerVec = { x: 0, y: 0 }; + currentPointerId = null; const a = Array.from(pointerEventCache.values())[0]; const b = Array.from(pointerEventCache.values())[1]; const distance = Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY); @@ -184,20 +229,21 @@ function onPointermove(ev: PointerEvent) { prevTwoTouchPointsDistance = 0; - const deltaX = ev.clientX - lastX; - const deltaY = ev.clientY - lastY; + if (currentPointerId === ev.pointerId) { + const deltaX = ev.clientX - lastX; + const deltaY = ev.clientY - lastY; - if (isZooming.value) { - translation.value.x += deltaX; - translation.value.y += deltaY; + if (isZooming.value) { + translation.value.x += deltaX; + translation.value.y += deltaY; + } + + pointerVec = { x: deltaX, y: deltaY }; + + lastX = ev.clientX; + lastY = ev.clientY; } - pointerVec = { x: deltaX, y: deltaY }; - console.log('pointerVec', pointerVec); - - lastX = ev.clientX; - lastY = ev.clientY; - return false; } @@ -206,6 +252,9 @@ function onPointerup(ev: PointerEvent) { imageEl.value.releasePointerCapture(ev.pointerId); prevTwoTouchPointsDistance = 0; isDragging = false; + if (currentPointerId === ev.pointerId) { + currentPointerId = null; + } onZoomGestureEnd(); } @@ -215,13 +264,16 @@ const doubleTapDetector = makeDoubleTapDetector((ev) => { pointerVec = { x: 0, y: 0 }; if (isZooming.value) { - size.value.width = defaultSize.width; - size.value.height = defaultSize.height; - translation.value.x = defaultTranslation.x; - translation.value.y = defaultTranslation.y; isZooming.value = false; + beginAnimation({ + width: defaultSize.width, + height: defaultSize.height, + x: defaultTranslation.x, + y: defaultTranslation.y, + }, 300); } else { - zoomInTo(ev.touches[0].clientX, ev.touches[0].clientY, 2); + isZooming.value = true; + zoomInTo(ev.touches[0].clientX, ev.touches[0].clientY, 2, true); } });