feat: 卡片滑动时的动画效果
This commit is contained in:
@@ -12,7 +12,13 @@
|
|||||||
v-for="slot in renderSlots"
|
v-for="slot in renderSlots"
|
||||||
:key="slot.key"
|
:key="slot.key"
|
||||||
class="swiper-card"
|
class="swiper-card"
|
||||||
:class="[`is-${slot.role}`, { 'is-current': slot.role === 'current' }]"
|
:class="[
|
||||||
|
`is-${slot.role}`,
|
||||||
|
{
|
||||||
|
'is-current': slot.role === 'current',
|
||||||
|
'is-fill-fade': slot.key === fillFadeKey,
|
||||||
|
},
|
||||||
|
]"
|
||||||
:style="slot.style"
|
:style="slot.style"
|
||||||
@tap="handleCardTap(slot)"
|
@tap="handleCardTap(slot)"
|
||||||
>
|
>
|
||||||
@@ -62,6 +68,7 @@ const emit = defineEmits(["update:modelValue", "change", "didSelectItem"]);
|
|||||||
|
|
||||||
const DURATION = 280;
|
const DURATION = 280;
|
||||||
const RECYCLE_FRAME_DELAY = 32;
|
const RECYCLE_FRAME_DELAY = 32;
|
||||||
|
const FILL_FADE_DURATION = 240;
|
||||||
const CLICK_THRESHOLD = 8;
|
const CLICK_THRESHOLD = 8;
|
||||||
const SWIPE_THRESHOLD = 60;
|
const SWIPE_THRESHOLD = 60;
|
||||||
|
|
||||||
@@ -79,11 +86,13 @@ const isAnimating = ref(false);
|
|||||||
const isRecycling = ref(false);
|
const isRecycling = ref(false);
|
||||||
const isTapCandidate = ref(false);
|
const isTapCandidate = ref(false);
|
||||||
const swipeStep = ref(0);
|
const swipeStep = ref(0);
|
||||||
|
const fillFadeKey = ref("");
|
||||||
|
|
||||||
let startX = 0;
|
let startX = 0;
|
||||||
let startY = 0;
|
let startY = 0;
|
||||||
let settleTimer = null;
|
let settleTimer = null;
|
||||||
let recycleTimer = null;
|
let recycleTimer = null;
|
||||||
|
let fillFadeTimer = null;
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const hasExternalModel = Object.prototype.hasOwnProperty.call(
|
const hasExternalModel = Object.prototype.hasOwnProperty.call(
|
||||||
instance?.vnode.props || {},
|
instance?.vnode.props || {},
|
||||||
@@ -131,6 +140,13 @@ function clearRecycleTimer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearFillFadeTimer() {
|
||||||
|
if (fillFadeTimer) {
|
||||||
|
clearTimeout(fillFadeTimer);
|
||||||
|
fillFadeTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getCircularDistance = (from, to, total) => {
|
const getCircularDistance = (from, to, total) => {
|
||||||
const forward = normalizeIndex(to - from, total);
|
const forward = normalizeIndex(to - from, total);
|
||||||
const backward = normalizeIndex(from - to, total);
|
const backward = normalizeIndex(from - to, total);
|
||||||
@@ -174,6 +190,8 @@ watch(
|
|||||||
isDragging.value = false;
|
isDragging.value = false;
|
||||||
isAnimating.value = false;
|
isAnimating.value = false;
|
||||||
isRecycling.value = false;
|
isRecycling.value = false;
|
||||||
|
fillFadeKey.value = "";
|
||||||
|
clearFillFadeTimer();
|
||||||
if (hasExternalModel) {
|
if (hasExternalModel) {
|
||||||
syncActiveCursorByActualIndex(props.modelValue);
|
syncActiveCursorByActualIndex(props.modelValue);
|
||||||
} else {
|
} else {
|
||||||
@@ -392,7 +410,9 @@ const finishSwipe = (step) => {
|
|||||||
clearSettleTimer();
|
clearSettleTimer();
|
||||||
settleTimer = setTimeout(() => {
|
settleTimer = setTimeout(() => {
|
||||||
const nextCursor = normalizeIndex(activeCursor.value + step, virtualCount.value);
|
const nextCursor = normalizeIndex(activeCursor.value + step, virtualCount.value);
|
||||||
|
const fillIndex = normalizeIndex(nextCursor + step, virtualCount.value);
|
||||||
isRecycling.value = true;
|
isRecycling.value = true;
|
||||||
|
fillFadeKey.value = getItemKey(fillIndex);
|
||||||
activeCursor.value = nextCursor;
|
activeCursor.value = nextCursor;
|
||||||
const actualIndex = getActualIndex(nextCursor);
|
const actualIndex = getActualIndex(nextCursor);
|
||||||
emit("update:modelValue", actualIndex);
|
emit("update:modelValue", actualIndex);
|
||||||
@@ -403,6 +423,11 @@ const finishSwipe = (step) => {
|
|||||||
isRecycling.value = false;
|
isRecycling.value = false;
|
||||||
recycleTimer = null;
|
recycleTimer = null;
|
||||||
}, RECYCLE_FRAME_DELAY);
|
}, RECYCLE_FRAME_DELAY);
|
||||||
|
clearFillFadeTimer();
|
||||||
|
fillFadeTimer = setTimeout(() => {
|
||||||
|
fillFadeKey.value = "";
|
||||||
|
fillFadeTimer = null;
|
||||||
|
}, FILL_FADE_DURATION);
|
||||||
}, DURATION);
|
}, DURATION);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -462,12 +487,14 @@ const handleTouchCancel = () => {
|
|||||||
if (!canSwipe.value) return;
|
if (!canSwipe.value) return;
|
||||||
clearSettleTimer();
|
clearSettleTimer();
|
||||||
clearRecycleTimer();
|
clearRecycleTimer();
|
||||||
|
clearFillFadeTimer();
|
||||||
deltaX.value = 0;
|
deltaX.value = 0;
|
||||||
isDragging.value = false;
|
isDragging.value = false;
|
||||||
isAnimating.value = false;
|
isAnimating.value = false;
|
||||||
isRecycling.value = false;
|
isRecycling.value = false;
|
||||||
isTapCandidate.value = false;
|
isTapCandidate.value = false;
|
||||||
swipeStep.value = 0;
|
swipeStep.value = 0;
|
||||||
|
fillFadeKey.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCardTap = (slot) => {
|
const handleCardTap = (slot) => {
|
||||||
@@ -478,6 +505,7 @@ const handleCardTap = (slot) => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
clearSettleTimer();
|
clearSettleTimer();
|
||||||
clearRecycleTimer();
|
clearRecycleTimer();
|
||||||
|
clearFillFadeTimer();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.swiper-card.is-fill-fade .card-shell {
|
||||||
|
animation: card-fill-fade-in 240ms ease-out both;
|
||||||
|
}
|
||||||
|
|
||||||
.card-shell {
|
.card-shell {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -95,3 +99,13 @@
|
|||||||
.is-single .swiper-stage {
|
.is-single .swiper-stage {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes card-fill-fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user