From 500bdda0e5193577130247fe7d7f41a0c7c1f2b8 Mon Sep 17 00:00:00 2001 From: chenchun Date: Mon, 27 Apr 2026 16:47:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(header):=20=E9=87=8D=E6=9E=84=E5=A4=B4?= =?UTF-8?q?=E9=83=A8=E7=BB=84=E4=BB=B6=E5=B9=B6=E6=B7=BB=E5=8A=A0=E5=BA=95?= =?UTF-8?q?=E9=83=A8=E5=AF=BC=E8=88=AA=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将头部组件中的logo动画逻辑提取为独立组件 添加底部导航栏并实现滚动显示逻辑 更新国际化文件中的app名称 --- app/components/common/AppLogo.vue | 116 ++++++++++ app/components/layouts/ArcticNewOneHeader.vue | 202 ++++++++---------- i18n/locales/en.json | 2 +- 3 files changed, 209 insertions(+), 111 deletions(-) create mode 100644 app/components/common/AppLogo.vue diff --git a/app/components/common/AppLogo.vue b/app/components/common/AppLogo.vue new file mode 100644 index 0000000..43ffcab --- /dev/null +++ b/app/components/common/AppLogo.vue @@ -0,0 +1,116 @@ + + + diff --git a/app/components/layouts/ArcticNewOneHeader.vue b/app/components/layouts/ArcticNewOneHeader.vue index f563f3c..5e85e93 100644 --- a/app/components/layouts/ArcticNewOneHeader.vue +++ b/app/components/layouts/ArcticNewOneHeader.vue @@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n' import gsap from 'gsap' import { ScrollTrigger } from 'gsap/ScrollTrigger' import { useAppStore } from '~/stores/app' +import AppLogo from '~/components/common/AppLogo.vue' gsap.registerPlugin(ScrollTrigger) // gsap.registerPlugin(Observer) @@ -24,29 +25,28 @@ const navItems = computed>(() => [ const topHeaderRef = ref(null) const bottomHeaderRef = ref(null) -const logoRef = ref(null) let topHeaderTween: gsap.core.Tween | null = null let bottomHeaderScrollTrigger: ReturnType | null = null -let logoObserver: ReturnType | null = null let isBottomHeaderVisible = false let fallbackTimer: ReturnType | null = null const setBottomHeaderVisible = (visible: boolean) => { - if (!bottomHeaderRef.value || isBottomHeaderVisible === visible) { - return + if (!bottomHeaderRef.value || isBottomHeaderVisible === visible) { + return + } + + isBottomHeaderVisible = visible + + gsap.to(bottomHeaderRef.value, { + yPercent: visible ? 0 : 150, + autoAlpha: visible ? 1 : 0, + duration: 0.45, + ease: 'power2.out', + overwrite: 'auto', + force3D: true, + }) } - isBottomHeaderVisible = visible - - gsap.to(bottomHeaderRef.value, { - yPercent: visible ? 0 : 140, - autoAlpha: visible ? 1 : 0, - duration: 0.35, - ease: 'power2.out', - overwrite: 'auto', - }) -} - // 定义一个供外部或延迟调用的入场动画方法 const playHeaderIntro = () => { if (!topHeaderRef.value) return @@ -93,45 +93,6 @@ const handleHeroReady = () => { playHeaderIntro() } -// Logo 文字Hover 动画 -const logoHover = () => { - if (!logoRef.value) return - - logoObserver?.kill() - - logoObserver = ScrollTrigger.observe({ - target: logoRef.value, - onHover: (self) => { - gsap.to(self.target.children[0] as HTMLElement, { - y: -10, - duration: 0.3, - ease: 'power2.out', - }) - gsap.to(self.target.children[1] as HTMLElement, { - y: 10, - duration: 0.3, - ease: 'power2.out', - opacity: 1, - visibility: 'visible', - }) - }, - onHoverEnd: (self) => { - gsap.to(self.target.children[0] as HTMLElement, { - y: 0, - duration: 0.3, - ease: 'power2.out', - }) - gsap.to(self.target.children[1] as HTMLElement, { - y: 0, - duration: 0.3, - ease: 'power2.out', - opacity: 0, - visibility: 'hidden', - }) - }, - }) -} - onMounted(() => { // 顶部导航在页面刚开始滚动的前 200px 内逐渐上移并隐藏。 topHeaderTween = gsap.to(topHeaderRef.value, { @@ -143,8 +104,10 @@ onMounted(() => { // 底部导航默认藏在视口下方,只有滚动到深处并向上滚时才出现。 gsap.set(bottomHeaderRef.value, { - yPercent: 140, + xPercent: -50, + yPercent: 150, autoAlpha: 0, + force3D: true }) bottomHeaderScrollTrigger = ScrollTrigger.create({ @@ -153,23 +116,20 @@ onMounted(() => { onUpdate: (self) => { const scrollY = self.scroll() - // 深度超过 2500px 后向上滚,显示未来用于深层内容的底部导航。 - if (scrollY > 2500 && self.direction === -1) { + // 深度超过 1000px 后向上滚,显示底部导航。 + if (scrollY > 1000 && self.direction === -1) { setBottomHeaderVisible(true) return } - // 回到 2300px 以内,或继续向下滚时,底部导航收回。 - if (scrollY <= 2300 || self.direction === 1) { + // 回到 1000px 以内,或继续向下滚时,底部导航收回。 + if (scrollY <= 1000 || self.direction === 1) { setBottomHeaderVisible(false) } }, }) ScrollTrigger.refresh() - - // Logo 文字Hover 动画 - logoHover() }) // 监听全局加载状态,当加载完成后再决定 Header 的进场时机 @@ -193,32 +153,23 @@ watch(() => appStore.isAppLoading, (isLoading) => { }, { immediate: true }) onBeforeUnmount(() => { - if (fallbackTimer) { - clearTimeout(fallbackTimer) - fallbackTimer = null - } - if (typeof window !== 'undefined') { - window.removeEventListener('hero-intro-done', handleHeroReady) - } - topHeaderTween?.scrollTrigger?.kill() - topHeaderTween?.kill() - bottomHeaderScrollTrigger?.kill() - logoObserver?.kill() - topHeaderTween = null - bottomHeaderScrollTrigger = null - logoObserver = null - isBottomHeaderVisible = false - if (topHeaderRef.value && bottomHeaderRef.value) { - gsap.killTweensOf([topHeaderRef.value, bottomHeaderRef.value]) - } - if (logoRef.value) { - const brand = logoRef.value.querySelector('#logo-brand') - const name = logoRef.value.querySelector('#logo-name') - if (brand && name) { - gsap.killTweensOf([brand, name]) + if (fallbackTimer) { + clearTimeout(fallbackTimer) + fallbackTimer = null } - } -}) + if (typeof window !== 'undefined') { + window.removeEventListener('hero-intro-done', handleHeroReady) + } + topHeaderTween?.scrollTrigger?.kill() + topHeaderTween?.kill() + bottomHeaderScrollTrigger?.kill() + topHeaderTween = null + bottomHeaderScrollTrigger = null + isBottomHeaderVisible = false + if (topHeaderRef.value && bottomHeaderRef.value) { + gsap.killTweensOf([topHeaderRef.value, bottomHeaderRef.value]) + } + }) + + diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 23c8b74..19d9090 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -1,6 +1,6 @@ { "app": { - "name": "ArcticNewOne", + "name": "北极新一", "brand": "ArcticNewOne", "description": "Explore infinite possibilities and modern experiences. Welcome to ArcticNewOne." },