From 98d380edd8e1da2f4eabcf25801aabf92b676b42 Mon Sep 17 00:00:00 2001 From: zoujing Date: Tue, 26 May 2026 22:25:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=A1=E7=89=87=E6=BB=91=E5=8A=A8?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E5=8A=A8=E7=94=BB=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Discovery/components/CardSwiper/index.vue | 30 ++++++++++++++++++- .../components/CardSwiper/styles/index.scss | 14 +++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/pages/Discovery/components/CardSwiper/index.vue b/src/pages/Discovery/components/CardSwiper/index.vue index 0c3823f..bd3bf40 100644 --- a/src/pages/Discovery/components/CardSwiper/index.vue +++ b/src/pages/Discovery/components/CardSwiper/index.vue @@ -12,7 +12,13 @@ v-for="slot in renderSlots" :key="slot.key" 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" @tap="handleCardTap(slot)" > @@ -62,6 +68,7 @@ const emit = defineEmits(["update:modelValue", "change", "didSelectItem"]); const DURATION = 280; const RECYCLE_FRAME_DELAY = 32; +const FILL_FADE_DURATION = 240; const CLICK_THRESHOLD = 8; const SWIPE_THRESHOLD = 60; @@ -79,11 +86,13 @@ const isAnimating = ref(false); const isRecycling = ref(false); const isTapCandidate = ref(false); const swipeStep = ref(0); +const fillFadeKey = ref(""); let startX = 0; let startY = 0; let settleTimer = null; let recycleTimer = null; +let fillFadeTimer = null; const instance = getCurrentInstance(); const hasExternalModel = Object.prototype.hasOwnProperty.call( instance?.vnode.props || {}, @@ -131,6 +140,13 @@ function clearRecycleTimer() { } } +function clearFillFadeTimer() { + if (fillFadeTimer) { + clearTimeout(fillFadeTimer); + fillFadeTimer = null; + } +} + const getCircularDistance = (from, to, total) => { const forward = normalizeIndex(to - from, total); const backward = normalizeIndex(from - to, total); @@ -174,6 +190,8 @@ watch( isDragging.value = false; isAnimating.value = false; isRecycling.value = false; + fillFadeKey.value = ""; + clearFillFadeTimer(); if (hasExternalModel) { syncActiveCursorByActualIndex(props.modelValue); } else { @@ -392,7 +410,9 @@ const finishSwipe = (step) => { clearSettleTimer(); settleTimer = setTimeout(() => { const nextCursor = normalizeIndex(activeCursor.value + step, virtualCount.value); + const fillIndex = normalizeIndex(nextCursor + step, virtualCount.value); isRecycling.value = true; + fillFadeKey.value = getItemKey(fillIndex); activeCursor.value = nextCursor; const actualIndex = getActualIndex(nextCursor); emit("update:modelValue", actualIndex); @@ -403,6 +423,11 @@ const finishSwipe = (step) => { isRecycling.value = false; recycleTimer = null; }, RECYCLE_FRAME_DELAY); + clearFillFadeTimer(); + fillFadeTimer = setTimeout(() => { + fillFadeKey.value = ""; + fillFadeTimer = null; + }, FILL_FADE_DURATION); }, DURATION); }; @@ -462,12 +487,14 @@ const handleTouchCancel = () => { if (!canSwipe.value) return; clearSettleTimer(); clearRecycleTimer(); + clearFillFadeTimer(); deltaX.value = 0; isDragging.value = false; isAnimating.value = false; isRecycling.value = false; isTapCandidate.value = false; swipeStep.value = 0; + fillFadeKey.value = ""; }; const handleCardTap = (slot) => { @@ -478,6 +505,7 @@ const handleCardTap = (slot) => { onBeforeUnmount(() => { clearSettleTimer(); clearRecycleTimer(); + clearFillFadeTimer(); }); diff --git a/src/pages/Discovery/components/CardSwiper/styles/index.scss b/src/pages/Discovery/components/CardSwiper/styles/index.scss index f4c6419..a751e53 100644 --- a/src/pages/Discovery/components/CardSwiper/styles/index.scss +++ b/src/pages/Discovery/components/CardSwiper/styles/index.scss @@ -20,6 +20,10 @@ will-change: transform, opacity; } +.swiper-card.is-fill-fade .card-shell { + animation: card-fill-fade-in 240ms ease-out both; +} + .card-shell { position: relative; width: 100%; @@ -95,3 +99,13 @@ .is-single .swiper-stage { overflow: visible; } + +@keyframes card-fill-fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +}