From d05972d229ac9a2c5029fc44e891e8cd9ac0e068 Mon Sep 17 00:00:00 2001 From: inottn Date: Sun, 14 May 2023 21:51:19 +0800 Subject: [PATCH] feat(ImagePreview): use the touched point as the center of zooming (#11848) --- .../src/image-preview/ImagePreviewItem.tsx | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/packages/vant/src/image-preview/ImagePreviewItem.tsx b/packages/vant/src/image-preview/ImagePreviewItem.tsx index a224c37aa..4a3b056cb 100644 --- a/packages/vant/src/image-preview/ImagePreviewItem.tsx +++ b/packages/vant/src/image-preview/ImagePreviewItem.tsx @@ -20,7 +20,7 @@ import { // Composables import { useTouch } from '../composables/use-touch'; -import { useEventListener } from '@vant/use'; +import { useEventListener, useRect } from '@vant/use'; // Components import { Image } from '../image'; @@ -33,6 +33,11 @@ const getDistance = (touches: TouchList) => (touches[0].clientY - touches[1].clientY) ** 2 ); +const getCenter = (touches: TouchList) => ({ + x: (touches[0].clientX + touches[1].clientX) / 2, + y: (touches[0].clientY + touches[1].clientY) / 2, +}); + const bem = createNamespace('image-preview')[1]; export default defineComponent({ @@ -62,6 +67,7 @@ export default defineComponent({ }); const touch = useTouch(); + const imageRef = ref(); const swipeItem = ref(); const vertical = computed(() => { @@ -77,9 +83,8 @@ export default defineComponent({ }; if (scale !== 1) { - const offsetX = moveX / scale; - const offsetY = moveY / scale; - style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`; + // use matrix to solve the problem of elements not rendering due to safari optimization + style.transform = `matrix(${scale}, 0, 0, ${scale}, ${moveX}, ${moveY})`; } return style; @@ -111,11 +116,31 @@ export default defineComponent({ return 0; }); - const setScale = (scale: number) => { + const setScale = (scale: number, center?: { x: number; y: number }) => { scale = clamp(scale, +props.minZoom, +props.maxZoom + 1); if (scale !== state.scale) { + const ratio = scale / state.scale; state.scale = scale; + + if (center) { + const imageRect = useRect(imageRef.value?.$el); + const origin = { + x: imageRect.width * 0.5, + y: imageRect.height * 0.5, + }; + const moveX = + state.moveX - (center.x - imageRect.left - origin.x) * (ratio - 1); + const moveY = + state.moveY - (center.y - imageRect.top - origin.y) * (ratio - 1); + + state.moveX = clamp(moveX, -maxMoveX.value, maxMoveX.value); + state.moveY = clamp(moveY, -maxMoveY.value, maxMoveY.value); + } else { + state.moveX = 0; + state.moveY = 0; + } + emit('scale', { scale, index: props.active, @@ -125,16 +150,17 @@ export default defineComponent({ const resetScale = () => { setScale(1); - state.moveX = 0; - state.moveY = 0; }; const toggleScale = () => { const scale = state.scale > 1 ? 1 : 2; - setScale(scale); - state.moveX = 0; - state.moveY = 0; + setScale( + scale, + scale === 2 + ? { x: touch.startX.value, y: touch.startY.value } + : undefined + ); }; let fingerNum: number; @@ -142,6 +168,7 @@ export default defineComponent({ let startMoveY: number; let startScale: number; let startDistance: number; + let lastCenter: { x: number; y: number }; let doubleTapTimer: ReturnType | null; let touchStartTime: number; let isImageMoved = false; @@ -170,7 +197,7 @@ export default defineComponent({ if (state.zooming) { startScale = state.scale; - startDistance = getDistance(event.touches); + startDistance = getDistance(touches); } }; @@ -207,8 +234,8 @@ export default defineComponent({ if (touches.length === 2) { const distance = getDistance(touches); const scale = (startScale * distance) / startDistance; - - setScale(scale); + lastCenter = getCenter(touches); + setScale(scale, lastCenter); } } }; @@ -279,7 +306,7 @@ export default defineComponent({ const maxZoom = +props.maxZoom; if (state.scale > maxZoom) { - state.scale = maxZoom; + setScale(maxZoom, lastCenter); } } } @@ -331,6 +358,7 @@ export default defineComponent({ ) : (