From 77567b2cc21c9fda925a8e9cd88858cc972e5b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=98=A5?= Date: Wed, 3 Jun 2026 20:10:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=9D=BF=E5=9D=97=E7=9A=84=20ScrollTrigger=20pin=20?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 从 HomeServices 开始往后的 6 个板块, 去掉 pin(固定)和 scrub(滚动驱动)效果, 改为正常的入场动画:滚动到时触发动画,播放完毕后正常滚动 修改的板块: - HomeServices: 卡片飞入归位(无 pin) - HomeAchievements: 逐个数字展示(无 pin) - HomeTestimonials: 卡片扩散入场(无 pin) - HomeTechStack: 标签散开入场(无 pin) - HomeProcess: 标题 + 线条 + 步骤入场(无 pin) - HomeUpdates: 卡片错开入场(无 pin) 保留 HomePartnersWork 的 pin 效果(原有板块) Co-Authored-By: Claude Opus 4.8 (1M context) --- app/components/home/HomeAchievements.vue | 20 +++--------- app/components/home/HomeProcess.vue | 33 +++++--------------- app/components/home/HomeServices.vue | 39 +++--------------------- app/components/home/HomeTechStack.vue | 22 +++---------- app/components/home/HomeTestimonials.vue | 35 ++++++--------------- app/components/home/HomeUpdates.vue | 38 +++++++---------------- 6 files changed, 43 insertions(+), 144 deletions(-) diff --git a/app/components/home/HomeAchievements.vue b/app/components/home/HomeAchievements.vue index 30a7ef4..531d3ca 100644 --- a/app/components/home/HomeAchievements.vue +++ b/app/components/home/HomeAchievements.vue @@ -213,35 +213,23 @@ onMounted(async () => { gsap.set(item, { autoAlpha: 0, x: index % 2 === 0 ? -100 : 100 }) }) - // 统一时间线:逐个展示,无空白 + // 入场动画(无 pin):逐个展示 const tl = gsap.timeline() - let cursor = 0 for (let i = 0; i < totalItems; i++) { - if (i > 0) { - // 前一个淡出,和后一个淡入在同一时间开始 - tl.to(achievementElements.value[i - 1], { - autoAlpha: 0, x: i % 2 === 0 ? 100 : -100, - duration: 0.6, ease: 'power2.in', - }, cursor) - } tl.to(achievementElements.value[i], { autoAlpha: 1, x: 0, duration: 0.8, ease: 'power3.out', onStart: () => { const numberEl = numberElements.value[i] if (numberEl) animateNumber(numberEl, achievements[i].number, achievements[i].suffix) } - }, cursor + 0.1) - cursor += 1 + }, i * 0.2) } ScrollTrigger.create({ trigger: rootRef.value, - start: 'top top', - end: () => `+=${cursor * window.innerHeight}`, - pin: true, - scrub: 0.5, - anticipatePin: 1, + start: 'top 75%', + toggleActions: 'play none none none', animation: tl, }) }, rootRef.value) diff --git a/app/components/home/HomeProcess.vue b/app/components/home/HomeProcess.vue index e374ab1..0e27b73 100644 --- a/app/components/home/HomeProcess.vue +++ b/app/components/home/HomeProcess.vue @@ -223,47 +223,30 @@ onMounted(async () => { gsap.set(step, { autoAlpha: 0, y: 60, scale: 0.85 }) }) - // 统一时间线:无空白 + // 入场动画(无 pin):标题 + 线条 + 步骤 const tl = gsap.timeline() - let cursor = 0 - // 第一屏:标题入场 - tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, cursor) - cursor += 1 + tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, 0) - // 第二屏:连接线绘制 + 所有步骤卡片出现 if (lineRef.value && linePathRef.value) { const pathLength = linePathRef.value.getTotalLength() gsap.set(linePathRef.value, { strokeDasharray: pathLength, strokeDashoffset: pathLength }) - tl.to(lineRef.value, { autoAlpha: 1, duration: 0.3 }, cursor) - tl.to(linePathRef.value, { strokeDashoffset: 0, duration: 1.5, ease: 'power2.inOut' }, cursor) + tl.to(lineRef.value, { autoAlpha: 1, duration: 0.3 }, 0.4) + tl.to(linePathRef.value, { strokeDashoffset: 0, duration: 1.5, ease: 'power2.inOut' }, 0.4) } + stepElements.value.forEach((step, index) => { if (!step) return tl.to(step, { autoAlpha: 1, y: 0, scale: 1, duration: 0.6, ease: 'back.out(1.5)', - }, cursor + index * 0.1) + }, 0.5 + index * 0.1) }) - cursor += 1 - - // 第三屏起:横向滚动 - if (scrollContainerRef.value) { - const container = scrollContainerRef.value.querySelector('.flex') - if (container) { - const scrollWidth = container.scrollWidth - window.innerWidth - tl.to(container, { x: -scrollWidth, duration: 2, ease: 'none' }, cursor) - cursor += 2 - } - } ScrollTrigger.create({ trigger: rootRef.value, - start: 'top top', - end: () => `+=${cursor * window.innerHeight}`, - pin: true, - scrub: 0.5, - anticipatePin: 1, + start: 'top 75%', + toggleActions: 'play none none none', animation: tl, }) }, rootRef.value) diff --git a/app/components/home/HomeServices.vue b/app/components/home/HomeServices.vue index accf37a..b3cd6b2 100644 --- a/app/components/home/HomeServices.vue +++ b/app/components/home/HomeServices.vue @@ -218,51 +218,22 @@ onMounted(async () => { gsap.set(card, { autoAlpha: 0, x: p.x, y: p.y, scale: 0.8, rotateZ: p.rotate }) }) - // 统一时间线:所有动画填满时间轴,无空白 - const totalCards = services.length + // 入场动画(无 pin) const tl = gsap.timeline() - let cursor = 0 // 时间游标 - // 第一屏:标题入场 + 所有卡片飞入归位 - tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, cursor) + tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, 0) cardElements.value.forEach((card, index) => { if (!card) return tl.to(card, { autoAlpha: 1, x: 0, y: 0, scale: 1, rotateZ: 0, duration: 0.8, ease: 'back.out(1.2)', - }, cursor + 0.1 + index * 0.08) + }, 0.2 + index * 0.1) }) - cursor += 1 - - // 后续屏:逐张聚焦,每步 duration=1 填满整步 - for (let focus = 0; focus < totalCards; focus++) { - cardElements.value.forEach((card, index) => { - if (!card) return - tl.to(card, { - scale: index === focus ? 1.05 : 0.85, - autoAlpha: index === focus ? 1 : 0.4, - duration: 0.8, ease: 'power2.out', - }, cursor) - tl.call(() => handleCardHover(index, index === focus), [], cursor + 0.2) - }) - cursor += 1 - } - - // 最后一屏:恢复正常 - cardElements.value.forEach((card, index) => { - if (!card) return - tl.to(card, { scale: 1, autoAlpha: 1, duration: 0.6, ease: 'power2.out' }, cursor) - tl.call(() => handleCardHover(index, false), [], cursor) - }) - cursor += 1 ScrollTrigger.create({ trigger: rootRef.value, - start: 'top top', - end: () => `+=${cursor * window.innerHeight}`, - pin: true, - scrub: 0.5, - anticipatePin: 1, + start: 'top 75%', + toggleActions: 'play none none none', animation: tl, }) }, rootRef.value) diff --git a/app/components/home/HomeTechStack.vue b/app/components/home/HomeTechStack.vue index 55b8c00..617eea6 100644 --- a/app/components/home/HomeTechStack.vue +++ b/app/components/home/HomeTechStack.vue @@ -200,33 +200,21 @@ onMounted(async () => { gsap.set(el, { autoAlpha: 0, scale: 0 }) }) - // 统一时间线:入场 + pin 旋转,无空白 + // 入场动画(无 pin):内容 + 标签散开 const tl = gsap.timeline() - let cursor = 0 - // 第一屏:内容入场 + 技术标签散开 - tl.to(contentRef.value, { autoAlpha: 1, x: 0, duration: 0.8, ease: 'power3.out' }, cursor) + tl.to(contentRef.value, { autoAlpha: 1, x: 0, duration: 0.8, ease: 'power3.out' }, 0) techElements.value.forEach((el, index) => { if (!el) return tl.to(el, { autoAlpha: 1, scale: 1, duration: 0.8, ease: 'back.out(2)', - }, cursor + 0.1 + index * 0.05) + }, 0.2 + index * 0.05) }) - cursor += 1 - - // 后续屏:球体旋转一圈 - tl.to(autoRotation.value, { - y: 360, duration: 2, ease: 'none', onUpdate: updatePositions, - }, cursor) - cursor += 2 ScrollTrigger.create({ trigger: rootRef.value, - start: 'top top', - end: () => `+=${cursor * window.innerHeight}`, - pin: true, - scrub: 1, - anticipatePin: 1, + start: 'top 75%', + toggleActions: 'play none none none', animation: tl, }) }, rootRef.value) diff --git a/app/components/home/HomeTestimonials.vue b/app/components/home/HomeTestimonials.vue index fdafdc8..5e187db 100644 --- a/app/components/home/HomeTestimonials.vue +++ b/app/components/home/HomeTestimonials.vue @@ -233,38 +233,23 @@ onMounted(async () => { gsap.set(card, { autoAlpha: 0, scale: 0.5 }) }) - // 统一时间线:入场 + pin 滚动切换,无空白 + // 入场动画(无 pin):标题 + 卡片从中心扩散 const totalCards = testimonials.length const tl = gsap.timeline() - let cursor = 0 - // 第一屏:标题入场 + 第一张卡片出现 - tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, cursor) - tl.to(cardElements.value[0], { autoAlpha: 1, scale: 1, duration: 0.8, ease: 'back.out(1.5)' }, cursor + 0.2) - tl.call(() => { currentIndex.value = 0 }, [], cursor + 0.2) - cursor += 1 + tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, 0) + cardElements.value.forEach((card, index) => { + if (!card) return + tl.to(card, { autoAlpha: 1, scale: 1, duration: 0.6, ease: 'back.out(1.5)' }, 0.3 + index * 0.08) + }) - // 后续屏:滚动切换卡片,每屏切一张 - for (let i = 1; i < totalCards; i++) { - // 当前卡片缩小淡出 - tl.to(cardElements.value[i - 1], { - autoAlpha: 0, scale: 0.5, duration: 0.5, ease: 'power2.in' - }, cursor) - // 新卡片放大淡入(紧接淡出开始,略有重叠) - tl.to(cardElements.value[i], { - autoAlpha: 1, scale: 1, duration: 0.6, ease: 'back.out(1.5)' - }, cursor + 0.2) - tl.call(() => { currentIndex.value = i }, [], cursor + 0.2) - cursor += 1 - } + // 初始化到第一张卡片 + tl.call(() => { currentIndex.value = 0 }, [], 0.3) ScrollTrigger.create({ trigger: rootRef.value, - start: 'top top', - end: () => `+=${cursor * window.innerHeight}`, - pin: true, - scrub: 0.5, - anticipatePin: 1, + start: 'top 75%', + toggleActions: 'play none none none', animation: tl, }) }, rootRef.value) diff --git a/app/components/home/HomeUpdates.vue b/app/components/home/HomeUpdates.vue index b027295..5dfe25d 100644 --- a/app/components/home/HomeUpdates.vue +++ b/app/components/home/HomeUpdates.vue @@ -171,39 +171,23 @@ onMounted(async () => { gsap.set(card, { autoAlpha: 0, y: 60, scale: 0.9 }) }) - // 统一时间线:无空白 + // 入场动画(无 pin):标题 + 卡片错开出现 const tl = gsap.timeline() - let cursor = 0 - // 第一屏:标题入场 - tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, cursor) - cursor += 1 + tl.to(headerRef.value, { autoAlpha: 1, y: 0, duration: 0.8, ease: 'power3.out' }, 0) - // 分批展示卡片:每批 2 个,逐批出现 - const totalCards = updates.length - const batchSize = 2 - const batches = Math.ceil(totalCards / batchSize) - - for (let batch = 0; batch < batches; batch++) { - const start = batch * batchSize - const end = Math.min(start + batchSize, totalCards) - - for (let i = start; i < end; i++) { - tl.to(cardElements.value[i], { - autoAlpha: 1, y: 0, scale: 1, - duration: 0.7, ease: 'power3.out', - }, cursor + (i - start) * 0.12) - } - cursor += 1 - } + cardElements.value.forEach((card, index) => { + if (!card) return + tl.to(card, { + autoAlpha: 1, y: 0, scale: 1, + duration: 0.7, ease: 'power3.out', + }, 0.3 + index * 0.1) + }) ScrollTrigger.create({ trigger: rootRef.value, - start: 'top top', - end: () => `+=${cursor * window.innerHeight}`, - pin: true, - scrub: 0.5, - anticipatePin: 1, + start: 'top 75%', + toggleActions: 'play none none none', animation: tl, }) }, rootRef.value)