本文首发于github.com/bigo-fronte...欢迎关注、转发。
前言
最近收到一个开发抽奖轮播活动页面的请求。由于轮播的用户界面比较特殊,很难用开源组件来定制,而轮播的特效其实实现起来并不困难,所以我使用原生js利用慢动作动画的原理开发了电唱机的特殊效果。开发过程中介绍了一些与引导功能相关的知识点,也总结了电唱机动画的实现原理。我希望我能提供一些帮助。
舒缓功能是什么
事物的运动遵循一定的规律。生活中,从汽车加速、减速、投球、自由落体等。可见,它不是直线运动,而是遵循一定规则的加速或减速过程。缓解函数描述了这种运动的特征。于是我们总结了一系列可以用来描述物体运动规律的数学公式。游戏、动画开发中有很多应用场景。
常见的缓解功能有:
直线运动
物体在同一时间内移动相同的距离。简而言之:可以认为是平稳运动,没有缓解作用
简单
物体最初移动速度非常慢,然后逐渐加速。在相同的时频范围内,运动距离增大,可以认为是加减速
冷静
物体最初移动速度非常快,然后逐渐减慢。在相同的时频范围内,运动距离减小,可以认为是减速
慢点慢点
这是轻松调节和均衡的结合。物体先加速后减速,这符合物体的自然运动规律。我在开发彩票转盘的运动动画时也使用了这个辅助功能
随着业务的发展,掌握应用便利化的公式就足够了。这里简单介绍一下缓解公式的使用。缓动函数的公式分为两个版本,原始版本和修改版本。原始版本必须指定4个参数。与原始版本相比,修改后的版本只需要一个参数,但结果是一个比例值,必须乘以总移动距离才能得到当前位置。
以二次函数化简为例:
// 原版
function QuadIn(t, b, c, d) {
return c * (t /= d) * t + b;
}
// t:缓动开始时间
// b:缓动开始位置
// c:缓动移动的距离
// d:缓动持续的时间
// 结果值为当前位置值
// 修改版
function QuadIn(p) {
return p * p;
}
// p: 当前已执行的时间 / 总时间
// 结果值需要与总移动距离相乘才获得当前位置
原版缓动公式集合:github.com/gdsmith/jqu…
修改版缓动公式集合:github.com/gdsmith/jqu…❙缓动功能快速查看
利用css实现闪电功能
虽然本文主要是用js实现简化功能动画,但这并不妨碍我们更多地了解其他实现方案。除了js实现之外,css是我们常用动画实现的首选方法。优点是降低了代码复杂度、浏览器优化、运行流畅、性能高,但缺点是缺乏对动画显示的灵活性和更精细的细节控制。我们通常使用CSS来指定动画运动函数和元素运动时间来描述整个运动过程。通过简单的定义,静态元素就可以变得可移动。
过渡
过渡动画突出显示了从开始到结束的过程。您只需要指定对象的开始和结束状态,并使用transition-timing-function属性指定缓动动画的名称。
.el {
transition-timing-function: ease-in-out;
}
动画
关键帧动画强调关键帧之间运动的控制。通过关键帧定义关键帧的状态,并使用animation-timing-function属性指定简化动画的名称。
.el {
animation-timing-function: ease-in-out;
}
使用贝塞尔曲线定义的浮雕函数
css的动画属性除了通过起伏函数的名称来定义外,还可以通过cubic-bezier()函数来定义。
三次贝塞尔曲线由四个点 P0、P1、P2 和 P3 定义,其中 P0 和 P3 表示起点 (0, 0) 和终点 (1, 1),P1 和 P2 分别表示两个锚点,通过调整P1和P2,可以绘制一条曲线,这条曲线就是缓动函数描述的运动特性。
.el {
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
}
创建三次贝塞尔曲线:cubic-bezier.com/
使用原生js实现缓动功能
下面的文章重点介绍使用js实现慢动作效果。动画的实现原理其实就是在每一帧中实现细微的动作。通过不断操作时间轴,实现过程的动画显示效果。基于这个原理,我们首先要在浏览器中实现动画循环功能。
动画循环功能
动画循环功能是以接近屏幕刷新率
的频率执行的帧内操作- 设置超时工具
const rAF = (callback) => {
return window.setTimeout(callback, 1000 / 60)
}
const amination = () => {
// 退出循环条件
if () {
return
}
rAF(amination);
}
rAF(amination);
- 设置间隔工具
const rAF = (callback) => {
return window.setInterval(callback, 1000 / 60)
}
const amination = () => {
// 退出循环条件
if () {
return window.clearInterval(this.timer);
}
}
this.timer = rAF(amination);
- 使用浏览器提供的
requestAnimationFrame
方法
const amination = () => {
// 退出循环条件
if () {
return
}
window.requestAnimationFrame(amination);
};
window.requestAnimationFrame(amination);
最好使用window.requestAnimationFrame
实现,浏览器可以根据屏幕刷新率强制执行,不会出现丢帧情况
电唱机的运动过程
转盘过程基本分为3个部分:1.启动加速2.匀速3.减速停止。然后用js来实现
加速阶段
转盘开始以最大速度旋转,从慢到快加速旋转速度的过程,我们可以通过Easy-in功能来处理旋转速度
// 缓入函数
function eaeIsn(t, b, c, d) {
if (t >= d) t = d;
return c * (t /= d) * t + b;
}
// 开始时间
const startTime = Date.now();
// 最大速度,将每帧旋转的角度为看做速率
const maxSpeed = 20;
// 加速持续时间
const holdTime = 3000;
// 时间段的起点
const timeStartPoint = 0;
// 旋转角度
this.deg = 0;
function animation() {
// 当前使用的时间段
const currentTime = Date.now() - startTime;
// 获取当前帧的速度
const curSpeed = easeIn(currentTime, timeStartPoint, maxSpeed, holdTime);
// 旋转角度
this.deg += curSpeed;
// 优化结果值,取360度的余数结果即为当前位置
this.deg = this.deg % 360;
window.requestAnimationFrame(animation)
}
window.requestAnimationFrame(animation);
恒速齿轮
此路段保持匀速即可
减速阶段
转盘从最高速度旋转到停止是旋转速度由快到慢减速的过程,可以用减速功能来处理
这个过程不仅仅是停止转盘的速度那么简单,还要考虑到转盘停在指定的地方才能获得相应的奖品。这里的处理逻辑是从停止停止的指定区域开始反转完整的旋转角度,然后使用easy-out函数处理旋转距离。下面是完整的动画处理流程
// 缓入函数
function eaeIsn(t, b, c, d) {
if (t >= d) t = d;
return c * (t /= d) * t + b;
}
// 缓出函数
function easeOut (t, b, c, d) {
if (t >= d) t = d;
return -c * (t /= d) * (t - 2) + b;
}
// 开始时间
const startTime = Date.now();
// 最大速度
const maxSpeed = 20;
// 加速持续时间
const startHoldTime = 2000;
// 时间段的起点
const timeStartPoint = 0;
// 减速持续时间
const endHoldTime = 3000;
// 旋转角度
this.deg = 0;
// 停止区域索引
this.stopIndex = 0;
// 动画循环总次数,用来计算fps
this.progress = 0;
// 减速开始时间
this.endTime = 0;
// 每个奖品所占的角度
this.prizeDeg = 45;
function animation() {
// 当前使用的时间段
const currentTime = Date.now() - startTime;
// 获取当前帧的速度
const curSpeed = easeIn(currentTime, timeStartPoint, maxSpeed, holdTime);
// 旋转角度
this.deg += curSpeed;
// 优化结果值,取360度的余数结果即为当前位置
this.deg = this.deg % 360;
// 检测到stopIndex有值,此时知道抽到的奖品区间范围,开始实行减速,计算减速总路程
if (this.stopIndex > 0) {
// 计算屏幕刷新帧率
const fps = currentTime / this.progress;
this.endTime = Date.now();
// 开始减速时所处的位置
this.stopDeg = this.deg;
let i = 0;
while(++i) {
// 结合开始减速时所处的位置和结束时所处的位置计算旋转总路程
const endDeg = 360 * i - this.stopIndex * this.prizeDeg - this.stopDeg;
// 计算刚开始第一帧旋转的角度,也就是初始速度
const curSpeed = easeOut(fps, stopDeg, endDeg, endHoldTime) - this.stopDeg;
// 当初始速度与当前旋转最大速度相等,即可获取总共需要旋转的角度
if (curSpeed >= maxSpeed) {
this.endDeg = endDeg;
break;
}
}
// 开始减速
return slowDown();
}
window.requestAnimationFrame(animation)
}
function slowDown() {
window.requestAnimationFrame(function() {
const currentTime = Date.now() - this.endTime;
// 减速完成
if (currentTime >= endHoldTime) {
return;
}
// 缓出减速
this.deg = easeOut(currentTime, this.stopDeg, this.endDeg, endHoldTime) % 360;
this.slowDown();
})
}
window.requestAnimationFrame(animation);
以上是电唱机实现的基本代码。重点是解决从加速到减速的平滑过渡以及最后落到指定位置
性能优化
此外,还对动画进行了一些性能优化。旋转使用transform的rotate属性,因此will-change
属性用于预先通知浏览器元素变化,优化可能的动作,提高动画执行效率
.el {
will-change: transform;
}
欢迎大家留言讨论,祝大家工作顺利,生活愉快!
我是大前端,下一期再见。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。