GSAP ScrollTrigger 爆发效果与背景动画的性能优化实战
本文记录了首页两个关键动效的性能优化过程:
- AppleBurstPerfect(滚动爆发遮罩效果)
- ThemeAwareBackground(全屏渐变+滤镜背景)
三处优化概览
- AppleBurstPerfect:用
animation: tl + scrub: true
将时间线与滚动强耦合;用 CSS 变量承载渐变颜色避免重建background
;隔离合成层以消除闪烁;让光斑扩散贯穿整个滚动区间并在尾段淡出。 - BackgroundGradientAnimation:新增
animated
/blurStrength
/interactive
三开关;仅在交互开启时创建 rAF;Safari 与跨浏览器的滤镜策略分离并按强度控制 blur。 - ThemeAwareBackground:结合
useMobilePerformance()
与prefers-reduced-motion
动态下调模糊强度与交互层,确保低端设备与减少动效用户的可用性与性能。
目标是保持原有效果质感,同时解决「滚一下就触发一次」「滚动中闪烁」「GPU/CPU 占用偏高」等问题。
问题与症状
- 一次性触发而非随滚动渐进:轻微滚动只触发一次爆发,无法随着滚轮逐步推进/回退。
- 阈值抖动导致闪烁:在临界点附近来回滚动,频繁切色/重绘造成闪烁。
- 背景滤镜成本高:大面积 blur + 混合模式在低端设备占用明显。
解决方案一:让动画严格跟随滚动(Scrub + 绑定时间线)
传统写法是在 onUpdate
中手动 tl.progress(self.progress)
,这容易与其它逻辑混杂。我们改为直接把时间线绑定到 ScrollTrigger
:
// src/components/ui/apple-burst-perfect.tsx
const tl = gsap.timeline({ paused: true })
.to(overlay, { "--burst-size": "100%", opacity: 1, duration: 1, ease: "power2.inOut" }, 0)
.to(overlay, { opacity: 0, duration: 0.15, ease: "power2.out" }, 0.85)
const st = ScrollTrigger.create({
trigger: hero,
start: "top top",
end: "bottom top",
pin: true,
pinSpacing: false,
scrub: true, // 严格跟随滚动,无中间缓冲
animation: tl, // 直接绑定时间线
anticipatePin: 1,
invalidateOnRefresh: true,
})
效果:时间线进度与滚动位移一一对应,滚上去就回退,完全消除「点一次触发」的违和感。
解决方案二:消除闪烁(避免重建背景,使用 CSS 变量)
频繁设置 element.style.background = radial-gradient(...)
会触发昂贵的样式/绘制更新并带来视觉跳变。我们将颜色抽象为 CSS 变量,只在初始化时设置一次背景,其后仅更新变量:
// 初始设置:
overlay.style.setProperty("--burst-c1", "#ffffff")
overlay.style.setProperty("--burst-c2", "#ffffff")
overlay.style.setProperty("--burst-size", "0%")
overlay.style.background =
"radial-gradient(circle at 50% 50%, var(--burst-c1) 0%, var(--burst-c2) 50%, transparent var(--burst-size))"
// 切换颜色时:
function updateBurstColor([c1, c2]: [string, string]) {
overlay.style.setProperty("--burst-c1", c1)
overlay.style.setProperty("--burst-c2", c2)
}
此外,我们通过以下方式提升合成稳定性:
overlay.style.willChange = "opacity, filter"
overlay.style.backfaceVisibility = "hidden"
overlay.style.transform = "translateZ(0)"
overlay.style.contain = "paint"
这些可显著降低重绘抖动,避免滚动中轻微闪屏。
解决方案三:节奏与层次(扩散贯穿全段、尾段淡出、内容渐显)
为了让用户感觉「滚动推动光斑扩散」:
- 扩散贯穿整个滚动区间:
duration: 1
表示占满整段滚动。 - 尾段淡出:在
0.85
处开始淡出,避免 unpin 时突兀。 - 内容过渡:Hero 在 0 起点淡出,Projects 卡片从 0.4 处上升显现(
duration: 0.6
)。
// timeline 片段
if (heroContent) {
tl.to(heroContent, { opacity: 0, duration: 0.35, ease: "power2.out" }, 0)
}
if (projectCards) {
tl.fromTo(projectCards,
{ y: 50, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.6, ease: "power2.out" },
0.4)
}
解决方案四:背景动画的性能护栏(ThemeAwareBackground)
背景层长驻页面,若无约束容易吞噬 GPU。我们为背景组件增加了性能参数与降级策略:
BackgroundGradientAnimation
新增animated
、blurStrength
、interactive
三个关键开关。- 仅在
interactive === true
时创建 rAF 交互层。 - Safari 与非 Safari 分别使用
blur
与 SVG filter,允许none/low/medium/high
颗粒度控制。 ThemeAwareBackground
结合useMobilePerformance()
与prefers-reduced-motion
自动下调:- 低端或减少动效:关闭交互层、弱化/移除 blur
- 中端:
blurStrength = medium
- 高端:维持中等强度,避免过度 GPU 开销
// src/components/ui/background-gradient-animation.tsx
export const BackgroundGradientAnimation = ({
animated = true,
blurStrength = "medium",
interactive = true,
...
}) => {
useEffect(() => {
if (!interactive) return
let raf = 0
const loop = () => { /* pointer 跟随 */ raf = requestAnimationFrame(loop) }
raf = requestAnimationFrame(loop)
return () => cancelAnimationFrame(raf)
}, [interactive])
}
// src/components/ui/theme-aware-background.tsx
const perf = useMobilePerformance()
const resolvedInteractive = interactive && !perf.prefersReducedMotion && !perf.isLowEnd
const resolvedAnimated = !perf.prefersReducedMotion && perf.currentTier !== "low"
const resolvedBlurStrength = perf.prefersReducedMotion ? "none" : perf.isLowEnd ? "low" : "medium"
最终调整清单(3 组件)
-
AppleBurstPerfect(
src/components/ui/apple-burst-perfect.tsx
)- 用
animation: tl + scrub: true
与滚动强绑定,彻底取消阈值触发。 - 用 CSS 变量
--burst-c1/--burst-c2/--burst-size
承载背景渐变,仅初始化一次background
,后续只改变量。 - 组合层优化:
willChange
、backface-visibility
、translateZ(0)
、contain: paint
。 - 节奏优化:扩散贯穿整段,0.85 处淡出;Hero 0 开始淡出;Projects 0.4 处起拉升。
- 用
-
BackgroundGradientAnimation(
src/components/ui/background-gradient-animation.tsx
)- 新增
animated
控制 CSS 动画是否启用。 - 新增
blurStrength
(none/low/medium/high),按浏览器与设备分档应用滤镜强度。 interactive
为真时才创建 rAF 指针追随循环;否则不创建 rAF。
- 新增
-
ThemeAwareBackground(
src/components/ui/theme-aware-background.tsx
)- 接入
useMobilePerformance()
与prefers-reduced-motion
:- 低端 / 减少动效:关闭交互层、
blurStrength=low/none
。 - 中端:
blurStrength=medium
,animated=true
。 - 高端:维持中等强度,避免过度 GPU 压力。
- 低端 / 减少动效:关闭交互层、
- 接入
成果与体验
- 滚动与动效实现一一对应,向上滚动即可回放,交互逻辑直观自然。
- 彻底移除临界点多次触发导致的闪烁。
- 背景层在低端/减少动效环境自动降级,整体 GPU/CPU 占用更平滑。
测量建议
- Performance 面板:录制 20s,观察合成与绘制占比。
- React Profiler:记录 Hero → Projects 的交互,确认重渲染是否减少。
- Lighthouse:关注 TBT/LCP 变化;背景降级通常能带来更稳定的指标。
相关文件
src/components/ui/apple-burst-perfect.tsx
src/components/ui/theme-aware-background.tsx
src/components/ui/background-gradient-animation.tsx
src/hooks/use-mobile-performance.ts
小结
本次优化的核心在于:
- 用
scrub + animation: tl
保证滚动与时间线强耦合 - 用 CSS 变量替代整字符串的背景重写,消除闪烁
- 加强合成层隔离,减少绘制抖动
- 为背景动效提供降级策略,持续优化感知性能
如果你也遇到类似的滚动闪烁与掉帧问题,不妨从这四个方向着手,往往能在保持视觉质感的同时拿到显著的性能收益。