实现一个轮盘抽奖滚动效果,整个滚动过程需要平滑滚动,滚动过程大概分为以下三步
- 1、启动轮盘滚动,从零开始加速运动
- 2、等待中奖结果返回、匀速滚动
- 3、中奖结果已经返回、减速滚动
function onStart() {
// 模拟异步请求,5秒后返回结果
setTimeout(() => {
canStop = true;
// 计算目标角度
spinIndex = Math.floor(Math.random() * prizes.value.length);
roundResultDeg = spinIndex * sectorAngle + sectorAngle / 2;
}, 5000);
// 开始滚动轮盘
startRotate();
}
使用 GSAP 时间轴分两个动画序列:
第一段:加速启动
.to(wheelElement, {
rotation: '+=90',
duration: 0.3,
ease: "power1.in",
})
- 快速旋转 90 度
- 使用
power1.in
缓动,模拟加速效果
第二段:匀速循环
.to(wheelElement, {
rotation: '+=360',
duration: 1,
ease: "none",
repeat: -1,
repeatDelay: 0,
onRepeat() {
rotateCounter++
},
onUpdate() {
if (rotateCounter >= 1 && canStop) {
onSpinEnd()
}
}
})
- 每秒转动 360 度(匀速)
- 无限循环(
repeat: -1
) - 实时监测是否可以停止
function onSpinEnd() {
...
tl.kill()
tl = gsap.timeline().to(wheelElement, {
rotation: roundResultDeg + '_cw',
duration,
ease: "power1.out",
onComplete() {
isSpinning.value = false
}
})
}
当前的轮盘动画实现存在速度不连续的问题,具体表现在两个关键衔接点:
- 加速阶段:90 度/0.3s,使用
power1.in
缓动 - 匀速阶段:360 度/1s,使用
none
(线性)
- 匀速阶段:360 度/s 的恒定角速度
- 减速阶段:使用
power1.out
从当前速度减速到 0
- 缺乏速度连续性计算:三段动画的参数(角度、时间、缓动函数)是独立设定的,没有考虑相邻段之间的速度衔接
- 轮盘转动过程中出现微小的顿挫感
- 视觉上可能产生不自然的加速/减速突变
要解决过渡平滑问题,必须先解决以下两个问题
- gsap 中的缓动函数名称对应的具体的函数,比如
power1.in
对应的函数是什么 - 基于缓动函数计算衔接点处的两个函数的导数,导数代表的变化率,也就是速度
分析 gsap 源码可知
power1.in
对应的函数为
函数求导为:
该运动函数在进度 1 处的导数为
none
对应的函数为
函数求导为:
该运动函数在进度 0 处的导数为
power1.out
对应函数为
函数求导为:
该运动函数在进度 0 处的导数为
以第一段动画为基准,计算第二段匀速动画应该设置的时间 第一段
rotation: '+=90',
duration: 0.3,
ease: "power1.in",
第二段
rotation: '+=360',
duration: 1, // 待计算调整
ease: "none",
很容计算出第二段匀速动画设置的时间
t = 360 / (90 / 0.3) / 2 = 0.6
所以参数需调整为
rotation: '+=360',
duration: 0.6,
ease: "none",
假设第三段动画需要额外转动的角度为 diff(该值需要基于中奖结果对应的角度和当前的角度进行差值运算)
计算第三算减速动画设置的时间
t = diff / (360 / 0.6) * 2
gsap 各类缓动函数的源码部分现已提取src/gsap-ease-functional.ts
文件中。根据源码可知
n (n > 1) 阶缓入函数:
即 n 阶缓入函数在起始阶段的速度为 0,在结束阶段的速度为 n
n (n > 1) 阶缓出函数:
设
即 n 阶缓出函数在起始阶段的速度为 n,在结束阶段的速度为 0
总结:
Power[n].in 在起始阶段速度为0, 在结束阶段速度为n
Power[n].out 在起始阶段速度为n, 在结束阶段速度为0
我们不仅有缓入函数和缓出函数,也存在这种先缓入再缓出的组合函数
比如Power2.inOut,通过分析源码可知其对应函数为两段分段函数组合而成,函数方程式和函数曲线如下
这种分段函数肯定也必须满足在衔接点的进度值和速度相同,也就是连续可导,否则这种组合函数也无法平滑过渡
进度值直接带入
我们需要证明以下分段函数在
当
当
这里
计算导数
(1) 对于
函数为:
- 设
$u = 2p$ ,则$f(p) = \frac{1}{2} u^{\text{power}}$ ,且$\frac{du}{dp} = 2$ 。 - 对
$p$ 求导:
于是,当
(2) 对于
函数为:
- 设
$v = 2(1 - p)$ ,则$f(p) = 1 - \frac{1}{2} v^{\text{power}}$ ,且$\frac{dv}{dp} = -2$ 。 - 对
$p$ 求导:
于是,当
通过以上计算,我们证明了对于任意