fix(cp活动): 修复苹果端奖励滚动动画闪烁问题

This commit is contained in:
hzj 2026-05-07 18:36:38 +08:00
parent 07afff8cf8
commit 55d49d026f

View File

@ -259,15 +259,9 @@
<!-- 奖励 --> <!-- 奖励 -->
<div class="reward-wrapper"> <div class="reward-wrapper">
<div <div ref="rewardViewportRef" class="reward-scroll-viewport">
style="height: 100%; display: flex; align-items: center; overflow: hidden"
>
<!-- 滚动容器 --> <!-- 滚动容器 -->
<div <div v-if="rewardList.length" class="scroll-wrapper">
v-if="rewardList.length"
class="scroll-wrapper"
:class="{ 'scroll-wrapper-active': scrollReady }"
>
<!-- 3套内容减少 iOS WebView 动画循环边界闪烁 --> <!-- 3套内容减少 iOS WebView 动画循环边界闪烁 -->
<div class="scroll-content" v-for="value in 3" :key="value"> <div class="scroll-content" v-for="value in 3" :key="value">
<div <div
@ -358,7 +352,7 @@ const props = defineProps({
}, },
}) })
const scrollReady = ref(false) const rewardViewportRef = ref(null)
const rewardList = computed(() => { const rewardList = computed(() => {
return Array.isArray(props.rewards?.activityRewardProps) ? props.rewards.activityRewardProps : [] return Array.isArray(props.rewards?.activityRewardProps) ? props.rewards.activityRewardProps : []
@ -388,6 +382,9 @@ const clickReward = (item) => {
topRewardShow.value = true // topRewardShow.value = true //
} }
let rewardRestartTimer = null let rewardRestartTimer = null
let rewardAnimationFrame = null
let rewardLastFrameTime = 0
let rewardLoopDistance = 0
const waitForLayoutFrame = () => { const waitForLayoutFrame = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -406,13 +403,52 @@ const restartRewardScroll = async () => {
if (rewardRestartTimer) { if (rewardRestartTimer) {
clearTimeout(rewardRestartTimer) clearTimeout(rewardRestartTimer)
} }
if (rewardAnimationFrame) {
cancelAnimationFrame(rewardAnimationFrame)
rewardAnimationFrame = null
}
scrollReady.value = false
await nextTick() await nextTick()
rewardRestartTimer = setTimeout(async () => { rewardRestartTimer = setTimeout(async () => {
await waitForLayoutFrame() await waitForLayoutFrame()
scrollReady.value = rewardList.value.length > 0 const viewport = rewardViewportRef.value
if (!viewport || !rewardList.value.length) {
rewardRestartTimer = null
return
}
const scrollCopies = viewport.querySelectorAll('.scroll-content')
if (scrollCopies.length < 2) {
rewardRestartTimer = null
return
}
rewardLoopDistance =
Math.abs(scrollCopies[1].offsetLeft - scrollCopies[0].offsetLeft) ||
scrollCopies[0].scrollWidth
if (!rewardLoopDistance) {
rewardRestartTimer = null
return
}
viewport.scrollLeft = 0
rewardLastFrameTime = performance.now()
const step = (now) => {
const elapsed = now - rewardLastFrameTime
rewardLastFrameTime = now
const distancePerMs = rewardLoopDistance / 8000
const nextScrollLeft = viewport.scrollLeft + distancePerMs * elapsed
viewport.scrollLeft =
nextScrollLeft >= rewardLoopDistance ? nextScrollLeft - rewardLoopDistance : nextScrollLeft
rewardAnimationFrame = requestAnimationFrame(step)
}
rewardAnimationFrame = requestAnimationFrame(step)
rewardRestartTimer = null rewardRestartTimer = null
}, 80) }, 80)
} }
@ -433,6 +469,10 @@ onUnmounted(() => {
clearTimeout(rewardRestartTimer) clearTimeout(rewardRestartTimer)
rewardRestartTimer = null rewardRestartTimer = null
} }
if (rewardAnimationFrame) {
cancelAnimationFrame(rewardAnimationFrame)
rewardAnimationFrame = null
}
}) })
</script> </script>
@ -470,13 +510,17 @@ onUnmounted(() => {
right: 15vw; right: 15vw;
} }
.reward-scroll-viewport {
height: 100%;
display: flex;
align-items: center;
overflow: hidden;
direction: ltr;
}
.scroll-wrapper { .scroll-wrapper {
display: flex; display: flex;
width: fit-content; width: fit-content;
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
} }
.scroll-content { .scroll-content {
@ -485,47 +529,15 @@ onUnmounted(() => {
align-items: center; align-items: center;
} }
@keyframes scroll-left {
0% {
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-33.333333%, 0, 0);
-webkit-transform: translate3d(-33.333333%, 0, 0);
}
}
@keyframes scroll-right {
0% {
transform: translate3d(0, 0, 0);
-webkit-transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(33.333333%, 0, 0);
-webkit-transform: translate3d(33.333333%, 0, 0);
}
}
[dir='ltr'] .scroll-content { [dir='ltr'] .scroll-content {
margin-left: 4vw; margin-left: 4vw;
} }
[dir='rtl'] .scroll-content { [dir='rtl'] .scroll-content {
margin-left: auto; direction: rtl;
margin-right: 4vw; margin-right: 4vw;
} }
[dir='ltr'] .scroll-wrapper-active {
animation: scroll-left 8s linear infinite;
will-change: transform;
}
[dir='rtl'] .scroll-wrapper-active {
animation: scroll-right 8s linear infinite;
will-change: transform;
}
@media screen and (max-width: 360px) { @media screen and (max-width: 360px) {
* { * {
font-size: 10px; font-size: 10px;