Code前端首页关于Code前端联系我们

使用原生js轻松实现彩票转盘动画

terry 2年前 (2023-09-08) 阅读数 173 #Vue
使用原生js通过缓动函数实现抽奖转盘动画使用原生js通过缓动函数实现抽奖转盘动画
前端开发工程师@bigo

filefile

本文首发于github.com/bigo-fronte...欢迎关注、转发。

前言

最近收到一个开发抽奖轮播活动页面的请求。由于轮播的用户界面比较特殊,很难用开源组件来定制,而轮播的特效其实实现起来并不困难,所以我使用原生js利用慢动作动画的原理开发了电唱机的特殊效果。开发过程中介绍了一些与引导功能相关的知识点,也总结了电唱机动画的实现原理。我希望我能提供一些帮助。

舒缓功能是什么

事物的运动遵循一定的规律。生活中,从汽车加速、减速、投球、自由落体等。可见,它不是直线运动,而是遵循一定规则的加速或减速过程。缓解函数描述了这种运动的特征。于是我们总结了一系列可以用来描述物体运动规律的数学公式。游戏、动画开发中有很多应用场景。

常见的缓解功能有:

直线运动

物体在同一时间内移动相同的距离。简而言之:可以认为是平稳运动,没有缓解作用

linearlinear

简单

物体最初移动速度非常慢,然后逐渐加速。在相同的时频范围内,运动距离增大,可以认为是加减速

ease-inease-in

冷静

物体最初移动速度非常快,然后逐渐减慢。在相同的时频范围内,运动距离减小,可以认为是减速

ease-outease-out

慢点慢点

这是轻松调节和均衡的结合。物体先加速后减速,这符合物体的自然运动规律。我在开发彩票转盘的运动动画时也使用了这个辅助功能

ease-in-outease-in-out

随着业务的发展,掌握应用便利化的公式就足够了。这里简单介绍一下缓解公式的使用。缓动函数的公式分为两个版本,原始版本和修改版本。原始版本必须指定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,可以绘制一条曲线,这条曲线就是缓动函数描述的运动特性。

cubic-beziercubic-bezier

.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来实现

drawPaneldrawPanel

加速阶段

转盘开始以最大速度旋转,从慢到快加速旋转速度的过程,我们可以通过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;
}
 

欢迎大家留言讨论,祝大家工作顺利,生活愉快!

我是大前端,下一期再见。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门