fix(动画): 修复首页动画重复播放和顶部导航显示问题
修复首页动画在特定条件下可能重复播放的问题,通过添加 hasPlayedIntro 状态控制 优化顶部导航的显示逻辑,增加即时显示和隐藏功能,并清理事件监听和定时器
This commit is contained in:
@@ -68,6 +68,7 @@ const playHeaderIntro = () => {
|
|||||||
duration: 1.4,
|
duration: 1.4,
|
||||||
ease: 'power4.out',
|
ease: 'power4.out',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
|
overwrite: 'auto',
|
||||||
clearProps: 'transform',
|
clearProps: 'transform',
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
// 动画完成时,强制把 style 的 opacity 移除,防止被内联样式锁死
|
// 动画完成时,强制把 style 的 opacity 移除,防止被内联样式锁死
|
||||||
@@ -76,6 +77,7 @@ const playHeaderIntro = () => {
|
|||||||
}
|
}
|
||||||
// 进场动画完毕后,再将滚动消失动画挂载到 ScrollTrigger
|
// 进场动画完毕后,再将滚动消失动画挂载到 ScrollTrigger
|
||||||
if (topHeaderTween) {
|
if (topHeaderTween) {
|
||||||
|
topHeaderTween.scrollTrigger?.kill()
|
||||||
ScrollTrigger.create({
|
ScrollTrigger.create({
|
||||||
animation: topHeaderTween,
|
animation: topHeaderTween,
|
||||||
start: 0,
|
start: 0,
|
||||||
@@ -93,6 +95,39 @@ const handleHeroReady = () => {
|
|||||||
playHeaderIntro()
|
playHeaderIntro()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cleanupHeroReadyHook = () => {
|
||||||
|
if (fallbackTimer) {
|
||||||
|
clearTimeout(fallbackTimer)
|
||||||
|
fallbackTimer = null
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('hero-intro-done', handleHeroReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideTopHeader = () => {
|
||||||
|
if (!topHeaderRef.value) return
|
||||||
|
gsap.set(topHeaderRef.value, { y: '100vh', opacity: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const showTopHeaderInstant = () => {
|
||||||
|
if (!topHeaderRef.value) return
|
||||||
|
|
||||||
|
gsap.set(topHeaderRef.value, { y: 0, opacity: 1 })
|
||||||
|
topHeaderRef.value.style.opacity = ''
|
||||||
|
|
||||||
|
if (topHeaderTween) {
|
||||||
|
topHeaderTween.scrollTrigger?.kill()
|
||||||
|
ScrollTrigger.create({
|
||||||
|
animation: topHeaderTween,
|
||||||
|
start: 0,
|
||||||
|
end: 200,
|
||||||
|
scrub: true,
|
||||||
|
})
|
||||||
|
ScrollTrigger.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 顶部导航在页面刚开始滚动的前 200px 内逐渐上移并隐藏。
|
// 顶部导航在页面刚开始滚动的前 200px 内逐渐上移并隐藏。
|
||||||
topHeaderTween = gsap.to(topHeaderRef.value, {
|
topHeaderTween = gsap.to(topHeaderRef.value, {
|
||||||
@@ -130,36 +165,34 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ScrollTrigger.refresh()
|
ScrollTrigger.refresh()
|
||||||
|
|
||||||
|
if (!appStore.isAppLoading && route.path !== '/') {
|
||||||
|
showTopHeaderInstant()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听全局加载状态,当加载完成后再决定 Header 的进场时机
|
watch([() => appStore.isAppLoading, () => route.path], ([isLoading, path]) => {
|
||||||
watch(() => appStore.isAppLoading, (isLoading) => {
|
if (isLoading || typeof window === 'undefined') return
|
||||||
if (!isLoading && typeof window !== 'undefined') {
|
|
||||||
if (route.path === '/') {
|
|
||||||
// 首页:等待自定义事件
|
|
||||||
window.addEventListener('hero-intro-done', handleHeroReady, { once: true })
|
|
||||||
|
|
||||||
// 增加一个保底的 setTimeout,防止首页的动画事件因为某种原因未能触发
|
cleanupHeroReadyHook()
|
||||||
// 从 loading 结束开始算起,首页的 intro 和 hero 动画加起来大约 3~4 秒
|
|
||||||
fallbackTimer = setTimeout(() => {
|
if (path === '/') {
|
||||||
window.removeEventListener('hero-intro-done', handleHeroReady)
|
hideTopHeader()
|
||||||
playHeaderIntro()
|
|
||||||
}, 5000)
|
window.addEventListener('hero-intro-done', handleHeroReady, { once: true })
|
||||||
} else {
|
|
||||||
// 非首页:加载完成后直接进场
|
fallbackTimer = setTimeout(() => {
|
||||||
|
window.removeEventListener('hero-intro-done', handleHeroReady)
|
||||||
playHeaderIntro()
|
playHeaderIntro()
|
||||||
}
|
}, 5000)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showTopHeaderInstant()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (fallbackTimer) {
|
cleanupHeroReadyHook()
|
||||||
clearTimeout(fallbackTimer)
|
|
||||||
fallbackTimer = null
|
|
||||||
}
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.removeEventListener('hero-intro-done', handleHeroReady)
|
|
||||||
}
|
|
||||||
topHeaderTween?.scrollTrigger?.kill()
|
topHeaderTween?.scrollTrigger?.kill()
|
||||||
topHeaderTween?.kill()
|
topHeaderTween?.kill()
|
||||||
bottomHeaderScrollTrigger?.kill()
|
bottomHeaderScrollTrigger?.kill()
|
||||||
|
|||||||
+14
-7
@@ -27,17 +27,24 @@ useSeoMeta({
|
|||||||
|
|
||||||
const homeIntroRef = ref<InstanceType<typeof HomeIntro> | null>(null)
|
const homeIntroRef = ref<InstanceType<typeof HomeIntro> | null>(null)
|
||||||
const homeHeroRef = ref<InstanceType<typeof HomeHero> | null>(null)
|
const homeHeroRef = ref<InstanceType<typeof HomeHero> | null>(null)
|
||||||
|
const hasPlayedIntro = ref(false)
|
||||||
|
|
||||||
// 监听全局加载状态,加载完成后依次播放开场和首屏动画
|
// 监听全局加载状态,加载完成后依次播放开场和首屏动画
|
||||||
watch(() => appStore.isAppLoading, async (isLoading) => {
|
watch(
|
||||||
if (!isLoading) {
|
() => [appStore.isAppLoading, homeIntroRef.value, homeHeroRef.value] as const,
|
||||||
await homeIntroRef.value?.play()
|
async ([isLoading, intro, hero]) => {
|
||||||
await homeHeroRef.value?.play()
|
if (hasPlayedIntro.value) return
|
||||||
|
if (isLoading || !intro || !hero) return
|
||||||
|
|
||||||
|
hasPlayedIntro.value = true
|
||||||
|
|
||||||
|
await intro.play()
|
||||||
|
await hero.play()
|
||||||
|
|
||||||
// 动画播放完毕后解除页面滚动锁定
|
|
||||||
isIntroActive.value = false
|
isIntroActive.value = false
|
||||||
}
|
},
|
||||||
}, { immediate: true })
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user