From 52112284cb632877584e9d486de827e28460178d Mon Sep 17 00:00:00 2001 From: chenchun Date: Mon, 27 Apr 2026 17:01:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=8A=A8=E7=94=BB):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E5=8A=A8=E7=94=BB=E9=87=8D=E5=A4=8D=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=92=8C=E9=A1=B6=E9=83=A8=E5=AF=BC=E8=88=AA=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复首页动画在特定条件下可能重复播放的问题,通过添加 hasPlayedIntro 状态控制 优化顶部导航的显示逻辑,增加即时显示和隐藏功能,并清理事件监听和定时器 --- app/components/layouts/ArcticNewOneHeader.vue | 77 +++++++++++++------ app/pages/index.vue | 23 ++++-- 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/app/components/layouts/ArcticNewOneHeader.vue b/app/components/layouts/ArcticNewOneHeader.vue index 5e85e93..8f10316 100644 --- a/app/components/layouts/ArcticNewOneHeader.vue +++ b/app/components/layouts/ArcticNewOneHeader.vue @@ -68,6 +68,7 @@ const playHeaderIntro = () => { duration: 1.4, ease: 'power4.out', delay: 0, + overwrite: 'auto', clearProps: 'transform', onComplete: () => { // 动画完成时,强制把 style 的 opacity 移除,防止被内联样式锁死 @@ -76,6 +77,7 @@ const playHeaderIntro = () => { } // 进场动画完毕后,再将滚动消失动画挂载到 ScrollTrigger if (topHeaderTween) { + topHeaderTween.scrollTrigger?.kill() ScrollTrigger.create({ animation: topHeaderTween, start: 0, @@ -93,6 +95,39 @@ const handleHeroReady = () => { 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(() => { // 顶部导航在页面刚开始滚动的前 200px 内逐渐上移并隐藏。 topHeaderTween = gsap.to(topHeaderRef.value, { @@ -130,36 +165,34 @@ onMounted(() => { }) ScrollTrigger.refresh() + + if (!appStore.isAppLoading && route.path !== '/') { + showTopHeaderInstant() + } }) -// 监听全局加载状态,当加载完成后再决定 Header 的进场时机 -watch(() => appStore.isAppLoading, (isLoading) => { - if (!isLoading && typeof window !== 'undefined') { - if (route.path === '/') { - // 首页:等待自定义事件 - window.addEventListener('hero-intro-done', handleHeroReady, { once: true }) +watch([() => appStore.isAppLoading, () => route.path], ([isLoading, path]) => { + if (isLoading || typeof window === 'undefined') return - // 增加一个保底的 setTimeout,防止首页的动画事件因为某种原因未能触发 - // 从 loading 结束开始算起,首页的 intro 和 hero 动画加起来大约 3~4 秒 - fallbackTimer = setTimeout(() => { - window.removeEventListener('hero-intro-done', handleHeroReady) - playHeaderIntro() - }, 5000) - } else { - // 非首页:加载完成后直接进场 + cleanupHeroReadyHook() + + if (path === '/') { + hideTopHeader() + + window.addEventListener('hero-intro-done', handleHeroReady, { once: true }) + + fallbackTimer = setTimeout(() => { + window.removeEventListener('hero-intro-done', handleHeroReady) playHeaderIntro() - } + }, 5000) + return } + + showTopHeaderInstant() }, { immediate: true }) onBeforeUnmount(() => { - if (fallbackTimer) { - clearTimeout(fallbackTimer) - fallbackTimer = null - } - if (typeof window !== 'undefined') { - window.removeEventListener('hero-intro-done', handleHeroReady) - } + cleanupHeroReadyHook() topHeaderTween?.scrollTrigger?.kill() topHeaderTween?.kill() bottomHeaderScrollTrigger?.kill() diff --git a/app/pages/index.vue b/app/pages/index.vue index 72cc296..ef5158b 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -27,17 +27,24 @@ useSeoMeta({ const homeIntroRef = ref | null>(null) const homeHeroRef = ref | null>(null) +const hasPlayedIntro = ref(false) // 监听全局加载状态,加载完成后依次播放开场和首屏动画 -watch(() => appStore.isAppLoading, async (isLoading) => { - if (!isLoading) { - await homeIntroRef.value?.play() - await homeHeroRef.value?.play() - - // 动画播放完毕后解除页面滚动锁定 +watch( + () => [appStore.isAppLoading, homeIntroRef.value, homeHeroRef.value] as const, + async ([isLoading, intro, hero]) => { + if (hasPlayedIntro.value) return + if (isLoading || !intro || !hero) return + + hasPlayedIntro.value = true + + await intro.play() + await hero.play() + isIntroActive.value = false - } -}, { immediate: true }) + }, + { immediate: true } +)