fix: 修复所有首页板块 pin 滚动不连贯问题

🐛 问题根源:
每个板块有两套独立的 ScrollTrigger 在同一个元素上:
- 入场动画用独立的 scrollTrigger
- pin 效果用 ScrollTrigger.create
两者互相冲突,导致空白间隙和动画跳变

 修复方案:
将所有板块的入场动画和 pin 滚动合并为一个统一时间线(timeline),
确保动画从 pin 开始到结束完全连续,无空白区

🔧 修复的 6 个板块:
1. HomeTestimonials - 标题入场 + 卡片逐张切换(淡入淡出)
2. HomeServices - 标题入场 + 卡片飞入归位 + 逐张聚焦
3. HomeAchievements - 逐个成就项展示(前一个淡出,后一个淡入)
4. HomeTechStack - 内容入场 + 标签散开 + 球体旋转
5. HomeProcess - 标题入场 + 线条绘制 + 步骤逐个出现 + 横向滚动
6. HomeUpdates - 标题入场 + 卡片分批出现

🎯 效果:
- pin 期间动画完全连续,无空白间隙
- 入场和 pin 共享同一个 timeline
- scrub: 0.5 让滚动更平滑
- 每个板块到下一个板块无缝过渡

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
陈春
2026-06-03 19:53:46 +08:00
parent bb0020b406
commit 02323fd426
6 changed files with 161 additions and 307 deletions
+18 -20
View File
@@ -206,43 +206,41 @@ onMounted(async () => {
const cleanupParticles = initParticles()
ctx = gsap.context(() => {
// Pin 效果:固定板块,逐个成就项展示
// 初始状态:全部隐藏
const totalItems = achievements.length
const timeline = gsap.timeline()
// 初始隐藏所有
achievementElements.value.forEach((item, index) => {
if (!item) return
gsap.set(item, { autoAlpha: 0, x: index % 2 === 0 ? -100 : 100 })
})
// 逐个展示每个成就项
for (let i = 0; i < totalItems; i++) {
timeline.addLabel(`item-${i}`)
// 统一时间线:逐个展示
const tl = gsap.timeline()
timeline.to(achievementElements.value[i], {
autoAlpha: 1,
x: 0,
duration: 0.8,
ease: 'power3.out',
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.5, ease: 'power2.in',
}, i)
}
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)
}
if (numberEl) animateNumber(numberEl, achievements[i].number, achievements[i].suffix)
}
}, `item-${i}`)
}, i + 0.2)
}
ScrollTrigger.create({
trigger: rootRef.value,
start: 'top top',
end: () => `+=${totalItems * window.innerHeight}`,
end: () => `+=${(totalItems + 1) * window.innerHeight}`,
pin: true,
scrub: true,
scrub: 0.5,
anticipatePin: 1,
animation: timeline,
animation: tl,
})
}, rootRef.value)