diff --git a/app/components/home/HomeHero.vue b/app/components/home/HomeHero.vue index 308ea99..8fca2ab 100644 --- a/app/components/home/HomeHero.vue +++ b/app/components/home/HomeHero.vue @@ -22,6 +22,57 @@ const studioNameRef = ref(null) const talkBtnRef = ref(null) let cleanupHeroScroll: (() => void) | null = null +let talkBtnTl: gsap.core.Timeline | null = null + +const initTalkButton = () => { + if (!talkBtnRef.value) return + + const hoverBg = talkBtnRef.value.querySelector('.btn-bg-hover') + const origChars = gsap.utils.toArray('.btn-char-orig', talkBtnRef.value) + const cloneChars = gsap.utils.toArray('.btn-char-clone', talkBtnRef.value) + + if (!hoverBg || !origChars.length || !cloneChars.length) return + + gsap.set(hoverBg, { yPercent: 110 }) + gsap.set(origChars, { yPercent: 0 }) + gsap.set(cloneChars, { yPercent: 110 }) +} + +const onTalkHover = () => { + if (!talkBtnRef.value) return + + const hoverBg = talkBtnRef.value.querySelector('.btn-bg-hover') + const origChars = gsap.utils.toArray('.btn-char-orig', talkBtnRef.value) + const cloneChars = gsap.utils.toArray('.btn-char-clone', talkBtnRef.value) + + if (!hoverBg || !origChars.length || !cloneChars.length) return + + talkBtnTl?.kill() + talkBtnTl = gsap.timeline({ defaults: { ease: 'power3.out' } }) + + talkBtnTl + .to(hoverBg, { yPercent: 0, duration: 0.42 }, 0) + .to(origChars, { yPercent: -115, duration: 0.42, stagger: 0.018 }, 0) + .to(cloneChars, { yPercent: 0, duration: 0.42, stagger: 0.018 }, 0) +} + +const onTalkLeave = () => { + if (!talkBtnRef.value) return + + const hoverBg = talkBtnRef.value.querySelector('.btn-bg-hover') + const origChars = gsap.utils.toArray('.btn-char-orig', talkBtnRef.value) + const cloneChars = gsap.utils.toArray('.btn-char-clone', talkBtnRef.value) + + if (!hoverBg || !origChars.length || !cloneChars.length) return + + talkBtnTl?.kill() + talkBtnTl = gsap.timeline({ defaults: { ease: 'power3.out' } }) + + talkBtnTl + .to(hoverBg, { yPercent: 110, duration: 0.42 }, 0) + .to(origChars, { yPercent: 0, duration: 0.42, stagger: 0.018 }, 0) + .to(cloneChars, { yPercent: 110, duration: 0.42, stagger: 0.018 }, 0) +} // 暴露供外部(如 index.vue)使用的异步播放方法 const play = () => new Promise((resolve) => { @@ -89,6 +140,7 @@ onMounted(() => { if (heroRef.value) gsap.set(heroRef.value, { yPercent: 100 }) // 初始化滚动监听逻辑 setupHeroScroll() + initTalkButton() }) const setupHeroScroll = () => { if (!heroStageRef.value || !heroPanelRef.value) return @@ -138,6 +190,7 @@ const setupHeroScroll = () => { onBeforeUnmount(() => { cleanupHeroScroll?.() + talkBtnTl?.kill() }) defineExpose({ play }) diff --git a/app/components/layouts/ArcticNewOneFooter.vue b/app/components/layouts/ArcticNewOneFooter.vue index e55fd0c..334333a 100644 --- a/app/components/layouts/ArcticNewOneFooter.vue +++ b/app/components/layouts/ArcticNewOneFooter.vue @@ -167,7 +167,7 @@ onUnmounted(() => { - + diff --git a/nuxt.config.ts b/nuxt.config.ts index 4de9236..915a2c4 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -6,6 +6,15 @@ export default defineNuxtConfig({ viewport: 'width=device-width, initial-scale=1, maximum-scale=1', }, }, + vite: { + optimizeDeps: { + include: [ + 'lenis', + 'gsap', + 'gsap/ScrollTrigger', + ], + }, + }, compatibilityDate: '2025-07-15', devtools: { enabled: true }, css: ['~/assets/css/tailwind.css'], diff --git a/package.json b/package.json index 82944bd..cf5f20a 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "vue-router": "^5.0.4" }, "devDependencies": { + "@iconify-json/ph": "1.2.2", "@nuxtjs/tailwindcss": "6.14.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8eb15d6..c49ef70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: specifier: ^5.0.4 version: 5.0.6(@vue/compiler-sfc@3.5.33)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3)))(vue@3.5.33(typescript@6.0.3)) devDependencies: + '@iconify-json/ph': + specifier: 1.2.2 + version: 1.2.2 '@nuxtjs/tailwindcss': specifier: 6.14.0 version: 6.14.0(magicast@0.5.2)(yaml@2.8.3) @@ -595,6 +598,9 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify-json/ph@1.2.2': + resolution: {integrity: sha512-PgkEZNtqa8hBGjHXQa4pMwZa93hmfu8FUSjs/nv4oUU6yLsgv+gh9nu28Kqi8Fz9CCVu4hj1MZs9/60J57IzFw==} + '@iconify/collections@1.0.676': resolution: {integrity: sha512-dtV8s0QtP0fjsIZoCA4gURBp2DWGs7vJDz+Kh5+vpeNinBbtbNz+MABDz61ikcFGE27mdvNFmA3ygprBXHZJ4A==} @@ -5348,6 +5354,10 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify-json/ph@1.2.2': + dependencies: + '@iconify/types': 2.0.0 + '@iconify/collections@1.0.676': dependencies: '@iconify/types': 2.0.0