Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 图片预览拖拽问题 #1104

Merged
merged 12 commits into from
Oct 25, 2023
134 changes: 117 additions & 17 deletions src/image-viewer/image-viewer.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<transition name="fade">
<div v-if="visible" :class="`${prefix}-image-viewer`">
<div v-if="visible" :ref="(el) => (rootRef = el)" :class="`${prefix}-image-viewer`">
<div :class="`${name}__mask`" @click="handleClose($event, 'overlay')" />
<div :class="`${name}__nav`">
<div v-if="closeNode" :class="`${name}__nav-close`" @click="handleClose($event, 'close-btn')">
Expand All @@ -18,6 +18,7 @@
:class="`${name}__content`"
height="100vh"
:default-current="currentIndex"
:disabled="disabled"
@change="onSwiperChange"
>
<t-swiper-item
Expand All @@ -39,7 +40,7 @@
</template>

<script lang="ts">
import { computed, defineComponent, reactive, getCurrentInstance, CSSProperties, h, Transition, toRefs } from 'vue';
import { computed, defineComponent, reactive, getCurrentInstance, h, Transition, toRefs, ref, watch } from 'vue';
import { CloseIcon, DeleteIcon } from 'tdesign-icons-vue-next';

import config from '../config';
Expand Down Expand Up @@ -76,6 +77,11 @@ export default defineComponent({
scale: 1,
touchIndex: 0,
swiperStyle: [] as string[],

dragging: false,
draggedX: 0,
draggedY: 0,
extraDraggedX: 0,
});
const [visible, setVisible] = useDefault(props, emit, 'visible', 'change');
const [currentIndex, setIndex] = useDefault<TdImageViewerProps['index'], TdImageViewerProps>(
Expand All @@ -85,6 +91,10 @@ export default defineComponent({
'index-change',
);
const touch = useTouch();

const disabled = ref(false);
const rootRef = ref();

const closeNode = computed(() =>
renderTNode(internalInstance, 'close-btn', {
defaultNode: h(CloseIcon),
Expand All @@ -97,16 +107,25 @@ export default defineComponent({
);

const imageTransform = computed(() => {
const { scale } = state;
return `scale(${scale}, ${scale})`;
const { scale, draggedX, draggedY } = state;
return `matrix(${scale}, 0, 0, ${scale}, ${draggedX}, ${draggedY})`;
});

const imageTransitionDuration = computed(() => {
const { zooming } = state;
return zooming ? 'transition-duration: 0s' : 'transition-duration: 0.3s';
const { zooming, dragging } = state;
return zooming || dragging ? 'transition-duration: 0s' : 'transition-duration: 0.3s';
});

const beforeClose = () => {
state.zooming = false;
state.scale = 1;
state.dragging = false;
state.draggedX = 0;
state.draggedY = 0;
};

const handleClose = (e: Event, trigger: string) => {
beforeClose();
setVisible(false);
emit('close', { trigger, e });
};
Expand All @@ -116,14 +135,33 @@ export default defineComponent({
};

const onSwiperChange = (index: number, context: any) => {
setIndex(index, { context });
if (currentIndex.value !== index) {
setIndex(index, { context });
setScale(1);
}
};

const maxDraggedX = computed(() => {
const rootOffsetWidth = rootRef.value?.offsetWidth || 0;
const scaledWidth = state.scale * rootOffsetWidth;
return Math.max(0, (scaledWidth - rootOffsetWidth) / 2);
});

const maxDraggedY = computed(() => {
const rootOffsetHeight = rootRef.value?.offsetHeight || 0;
const scaledHeight = state.scale * rootOffsetHeight;
return Math.max(0, (scaledHeight - rootOffsetHeight) / 2);
});

let fingerNum: number;
let startScale: number;
let startDistance: number;
let doubleTapTimer: number | null;
let touchStartTime: number;
let startDraggedX: number;
let startDraggedY: number;
let isDragged = false;

const onTouchStart = (event: TouchEvent, index: number) => {
preventDefault(event, true);
const { touches } = event;
Expand All @@ -132,8 +170,13 @@ export default defineComponent({

fingerNum = touches.length;
touchStartTime = Date.now();
startDraggedX = state.draggedX;
startDraggedY = state.draggedY;

state.dragging = fingerNum === 1;
state.zooming = fingerNum === 2;
state.touchIndex = index;

if (state.zooming) {
startScale = state.scale;
startDistance = getDistance(event.touches);
Expand All @@ -144,15 +187,30 @@ export default defineComponent({
const { touches } = event;

touch.move(event);
preventDefault(event, true);
if (state.zooming) {
preventDefault(event, true);
if (touches.length === 2) {
const distance = getDistance(touches);
const scale = (startScale * distance) / startDistance;

setScale(scale);
}
}
if (state.zooming && touches.length === 2) {
const distance = getDistance(touches);
const scale = (startScale * distance) / startDistance;

setScale(scale);
if (state.dragging) {
const { deltaX, deltaY, isHorizontal } = touch;
const draggedX = deltaX.value + startDraggedX;
const draggedY = deltaY.value + startDraggedY;
state.extraDraggedX = draggedX;
if ((draggedX > maxDraggedX.value || draggedX < -maxDraggedX.value) && !isDragged && isHorizontal()) {
state.dragging = false;
return;
}

isDragged = true;
preventDefault(event, true);
state.draggedX = Math.min(Math.max(draggedX, -maxDraggedX.value), maxDraggedX.value);
state.draggedY = Math.min(Math.max(draggedY, -maxDraggedY.value), maxDraggedY.value);
}
};

Expand All @@ -161,6 +219,8 @@ export default defineComponent({

if (scale !== state.scale) {
state.scale = scale;
state.draggedX = 0;
state.draggedY = 0;
}
};
const resetScale = () => {
Expand All @@ -178,7 +238,7 @@ export default defineComponent({
return;
}

resetScale();
// resetScale();

const { offsetX, offsetY } = touch;
const deltaTime = Date.now() - touchStartTime;
Expand All @@ -200,14 +260,27 @@ export default defineComponent({
};

const onTouchEnd = (event: TouchEvent) => {
// eliminate tap delay on safari
preventDefault(event, false);
if (state.zooming) {
event.stopPropagation();
let stopPropagation = false;

if (state.zooming || state.dragging) {
stopPropagation = true;

if (state.dragging && startDraggedX === state.draggedX && startDraggedY === state.draggedY) {
stopPropagation = false;
}

if (!event.touches.length) {
if (state.zooming) {
state.draggedX = Math.min(Math.max(state.draggedX, -maxDraggedX.value), maxDraggedX.value);
state.draggedY = Math.min(Math.max(state.draggedY, -maxDraggedY.value), maxDraggedY.value);
state.zooming = false;
}

state.dragging = false;
startDraggedX = 0;
startDraggedY = 0;
startScale = 1;

startScale = 1;
if (state.scale < 1) {
resetScale();
Expand All @@ -218,11 +291,38 @@ export default defineComponent({
}
}

// eliminate tap delay on safari
preventDefault(event, stopPropagation);

checkTap(event);
touch.reset();
};

watch(
() => state.scale,
(newVal) => {
if (newVal !== 1) {
disabled.value = true;
} else {
disabled.value = false;
}
},
);

watch(
() => state.extraDraggedX,
(newVal) => {
if (newVal > maxDraggedX.value || newVal < -maxDraggedX.value) {
disabled.value = false;
} else {
disabled.value = true;
}
},
);

return {
rootRef,
disabled,
name,
...toRefs(state),
prefix,
Expand Down
5 changes: 5 additions & 0 deletions src/swiper/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ export default {
return ['default', 'card'].includes(val);
},
},
/** 是否禁用 */
disabled: {
type: Boolean as PropType<TdSwiperProps['disabled']>,
default: false,
},
/** 轮播切换时触发 */
onChange: Function as PropType<TdSwiperProps['onChange']>,
/** 点击轮播项时触发 */
Expand Down
10 changes: 9 additions & 1 deletion src/swiper/swiper-item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ const direction = computed(() => (isVertical.value ? 'Y' : 'X'));
const calcTranslateStyle = (index: number, activeIndex: number) => {
const distance = root.value?.[isVertical.value ? 'offsetHeight' : 'offsetWidth'] ?? 0;
const lastItemIndex = items.value.length - 1;
let step = activeIndex === lastItemIndex && index === 0 ? 1 : index - activeIndex;
let step = index - activeIndex;
fennghuang marked this conversation as resolved.
Show resolved Hide resolved
// lastItem
if (activeIndex === lastItemIndex && index === 0) {
step = 1;
}
// firstItem
if (activeIndex === 0 && index === lastItemIndex) {
step = -1;
}

if (activeIndex === index) step = 0;

Expand Down
Loading
Loading