转盘游戏屡见不鲜,不管是菠菜网站,还是各类电商游戏app,都是一种效果特别好的营销手段。在种类来说的话,无论是圆形转盘,九宫格,还是老虎机,都有各种各样的展现方式,实现难度相对来说也没有那么大,那在这些展现方式中,发挥着较大作用的就是转动过程中的动画效果,动画之所以生动,其中一方面就是符合现实世界流畅自然的运动效果,变化之间有着比较舒服的过渡,本文主要讨论如何实现这种舒服的过渡
转盘动画的基本原理
关于转盘通过旋最终停到指定位置,想必大家都很容易理解,就是360/(奖品个数) *(目标位置索引)(只讨论指针指向单个奖品中间),那么动画如何停下来也有很多种方式,一种就是匀速停止,给人一种戛然而止的感觉,不太符合现实世界中的物理表现形式,那么另一种则是缓慢停止。
缓动函数介绍
提到缓慢停止,我们会想到一个概念叫缓动函数,大家一定无论在css中还是在其他动画效果的实现中都遇到过,缓动函数解决的问题就是在控制一个值在某一段时间的变化量,类似物理学中的加速度。换到转盘的场景里,常规情况下他会匀速到达某一个角度,当我们能把匀速改为变速后,动画效果会自然流畅很多。
常见的实现方案和不足
方案一:CSS实现
一种方法是使用css的easing-function
如设置
.block {
transition: transform 0.6s cubic-bezier(0.87, 0, 0.13, 1);
}
再根据属性的变化让他自动呈现
方案二:JavaScript定时器实现
第二种是社区比较常见的,使用setInterval的方法去控制变速,比如
// 初始化当前旋转角度为 0
let current = 0
// 定义最终旋转角度为 1800 度
const final = 1800
// 定义减速比例为 0.
let speed = 40
// 设置一个定时器,每 16 毫秒执行一次函数
const timer = setInterval(() => {
// 如果当前旋转角度大于等于最终旋转角度,清除定时器
if (current >= final)
clearInterval(timer)
current += speed
// 速度在每次角度计算后,减小0.1,数值仅举例说明,实际可能需要更加精细的调整
speed -= 0.1
}, 16)
以上代码表示速度变化率为0.1,如果在函数曲线上表示就是v=-0.1t,开口向下的抛物线
速度一开始最大,然后缓慢变小,相比起线性减小,多了一些柔和和流畅性。
当然也有这样写的,
if(current > 某个值){
speed = 20
}
if(current > 某个值){
speed = 10
}
手动控制每一阶段的速度值,这就像是之前学过的分段函数。
缺点
那么这些写法有以下几点不足之处:
- 边界条件需要自己处理
- 我要控制速度不能为0,还要保证动画执行完。合理
- 灵活性差,难以维护
- 如果角度变化,那对应的增量也需要进行相应的变化
- 状态难以维护,难以切换不同属性的不同动画形式,需要进行频繁的样式修改
React Spring实现
鉴于此,我们引入react-spring,react-spring是一个用于在 React 应用中创建动画效果的库。它基于物理模型实现动画效果。除了常见的可以通过配置摩擦力惯性以及质量控制动画的运动之外,还可以通过传入缓动函数的方式自定义动画效果。
在react-spring中,我们可以通过配置,对颜色,旋转,缩放等各种属性进行缓动,方法也很简单,指定好起始点,设置好对应的缓动函数即可
这里我们通过easing配置项去控制旋转值的变化:
function Component() {
const award_list = [] // 奖品列表
// 获取单个奖品旋转角度
const segmentAngle = 360 / award_list.length
const [style, api] = useSpring(() => ({
transform: 'rotate(0deg)',
}))
const start = () => {
const selectedItemIndex = 5 // 假设旋转到第5个商品
const endRotation = (3 * 360) + segmentAngle * selectedItemIndex
// 旋转方法
api.start(() => ({
transform: `rotate(${endRotation}deg)`,
config: {
duration: 5000,
easing,
},
}))
}
return <animated.div style={style} />
}
easing: EasingFunction
type EasingFunction = (t: number) => number
我们的目标是通过指定合适的easing函数,让rotate的值实现变速增长
缓动函数详解
easing 函数用于描述动画进度(或时间)和动画的补间值之间的映射关系。easing 函数接收一个参数
t,它表示动画的当前进度,范围从 0 到 1。这个函数返回一个新的值,通常也是在 0 到 1 的范围内,用来决定动画在该进度点上的状态。
所以我们把rotate从0到target度数,映射到了0到1,这样我们只需要看0到1上t的变化就可以了.
所以easing函数中t的变化趋势,就反映了目标属性的变化趋势
例如 const easing = t => t,想必大家都很容易理解,函数图像为一条直线
所以他的函数为 y = t(0<=t<=1) 变化率为y'=1,是匀速变化也就表现出来以下这种戛然而止的效果:
上述的函数图像在我们之前学习css的贝塞尔曲线时会有熟悉,例如在css贝塞尔曲线这个在线网页上呈现的效果,所以在这里我们可以理解为,当t的值越靠近1,斜率越小,y的增量就会越来越小,那动画就能实现这种缓慢停止的效果了,
我们从Easing Functions网站上的easeOutExpo这个效果为例,他的表达式为
函数图像为
我们可以看到t越靠近1,y到1的变化会越来越慢(斜率越来越小) 当然这里需要做一下边界处理,因为t=1时,y还没等于1,所以在js中的函数形式为:
function easeOutExpo(x: number): number {
return x === 1 ? 1 : 1 - 2 ** (-10 * x)
}
最终效果则为这样:
当然这里为了节约时间,我只调了转动2圈,转动时长为2s作为演示,在实际使用中,可以将这两个值加大(如转3圈,时长5s),效果会更好。
此外这个缓动函数网站上还有各种形式的缓动函数,例如你可以选一个先慢后快最后慢的效果(easeInOutExpo),其实这是上述一次函数分段函数的二次形式,大家可以选择不同的函数自己玩一下
总结与建议
- 动效的核心是节奏
react-spring提供了一种对属性的过渡动画间做插值解决办法,我们只需要定义好起止点以及相对应的缓动函数,即可实现该效果- 除了
react-spring外,所有能指定缓动函数的动画库都能实现此效果 - 底层可能都用了
setInterval或者requestanimationframe,那么还是使用封装后的工具更好一些