feat(ImagePreview): use the touched point as the center of zooming (#11848)

This commit is contained in:
inottn 2023-05-14 21:51:19 +08:00 committed by GitHub
parent 127b5b28d3
commit d05972d229
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -20,7 +20,7 @@ import {
// Composables // Composables
import { useTouch } from '../composables/use-touch'; import { useTouch } from '../composables/use-touch';
import { useEventListener } from '@vant/use'; import { useEventListener, useRect } from '@vant/use';
// Components // Components
import { Image } from '../image'; import { Image } from '../image';
@ -33,6 +33,11 @@ const getDistance = (touches: TouchList) =>
(touches[0].clientY - touches[1].clientY) ** 2 (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]; const bem = createNamespace('image-preview')[1];
export default defineComponent({ export default defineComponent({
@ -62,6 +67,7 @@ export default defineComponent({
}); });
const touch = useTouch(); const touch = useTouch();
const imageRef = ref<ComponentInstance>();
const swipeItem = ref<ComponentInstance>(); const swipeItem = ref<ComponentInstance>();
const vertical = computed(() => { const vertical = computed(() => {
@ -77,9 +83,8 @@ export default defineComponent({
}; };
if (scale !== 1) { if (scale !== 1) {
const offsetX = moveX / scale; // use matrix to solve the problem of elements not rendering due to safari optimization
const offsetY = moveY / scale; style.transform = `matrix(${scale}, 0, 0, ${scale}, ${moveX}, ${moveY})`;
style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`;
} }
return style; return style;
@ -111,11 +116,31 @@ export default defineComponent({
return 0; return 0;
}); });
const setScale = (scale: number) => { const setScale = (scale: number, center?: { x: number; y: number }) => {
scale = clamp(scale, +props.minZoom, +props.maxZoom + 1); scale = clamp(scale, +props.minZoom, +props.maxZoom + 1);
if (scale !== state.scale) { if (scale !== state.scale) {
const ratio = scale / state.scale;
state.scale = 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', { emit('scale', {
scale, scale,
index: props.active, index: props.active,
@ -125,16 +150,17 @@ export default defineComponent({
const resetScale = () => { const resetScale = () => {
setScale(1); setScale(1);
state.moveX = 0;
state.moveY = 0;
}; };
const toggleScale = () => { const toggleScale = () => {
const scale = state.scale > 1 ? 1 : 2; const scale = state.scale > 1 ? 1 : 2;
setScale(scale); setScale(
state.moveX = 0; scale,
state.moveY = 0; scale === 2
? { x: touch.startX.value, y: touch.startY.value }
: undefined
);
}; };
let fingerNum: number; let fingerNum: number;
@ -142,6 +168,7 @@ export default defineComponent({
let startMoveY: number; let startMoveY: number;
let startScale: number; let startScale: number;
let startDistance: number; let startDistance: number;
let lastCenter: { x: number; y: number };
let doubleTapTimer: ReturnType<typeof setTimeout> | null; let doubleTapTimer: ReturnType<typeof setTimeout> | null;
let touchStartTime: number; let touchStartTime: number;
let isImageMoved = false; let isImageMoved = false;
@ -170,7 +197,7 @@ export default defineComponent({
if (state.zooming) { if (state.zooming) {
startScale = state.scale; startScale = state.scale;
startDistance = getDistance(event.touches); startDistance = getDistance(touches);
} }
}; };
@ -207,8 +234,8 @@ export default defineComponent({
if (touches.length === 2) { if (touches.length === 2) {
const distance = getDistance(touches); const distance = getDistance(touches);
const scale = (startScale * distance) / startDistance; const scale = (startScale * distance) / startDistance;
lastCenter = getCenter(touches);
setScale(scale); setScale(scale, lastCenter);
} }
} }
}; };
@ -279,7 +306,7 @@ export default defineComponent({
const maxZoom = +props.maxZoom; const maxZoom = +props.maxZoom;
if (state.scale > maxZoom) { if (state.scale > maxZoom) {
state.scale = maxZoom; setScale(maxZoom, lastCenter);
} }
} }
} }
@ -331,6 +358,7 @@ export default defineComponent({
) : ( ) : (
<Image <Image
v-slots={imageSlots} v-slots={imageSlots}
ref={imageRef}
src={props.src} src={props.src}
fit="contain" fit="contain"
class={bem('image', { vertical: vertical.value })} class={bem('image', { vertical: vertical.value })}