refactor: 移除新增板块的 ScrollTrigger pin 效果

从 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) <noreply@anthropic.com>
This commit is contained in:
陈春
2026-06-03 20:10:40 +08:00
parent e94f7d92e9
commit 77567b2cc2
6 changed files with 43 additions and 144 deletions
+4 -16
View File
@@ -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)
+8 -25
View File
@@ -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)
+5 -34
View File
@@ -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)
+5 -17
View File
@@ -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)
+10 -25
View File
@@ -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)
+9 -25
View File
@@ -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], {
cardElements.value.forEach((card, index) => {
if (!card) return
tl.to(card, {
autoAlpha: 1, y: 0, scale: 1,
duration: 0.7, ease: 'power3.out',
}, cursor + (i - start) * 0.12)
}
cursor += 1
}
}, 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)