补间动画实现点赞效果

111 阅读2分钟

补间动画

补间动画是指在Flash的时间帧面板上,在一个关键帧上放置一个元件,然后在另一个关键帧
改变这个元件的大小、颜色、位置、透明度等,Flash 将自动根据二者**之间的**帧的值创建的**动画**

tween.js

Tween.js是一个包含各种经典动画算法的JS资源,,AS中甚至有专门的Tween类。`Quad``Cubic`等等
都是经典的动画运动算法名称,完整列表如下:
  1. Linear:线性匀速运动效果;
  2. Quadratic:二次方的缓动(t^2);
  3. Cubic:三次方的缓动(t^3);
  4. Quartic:四次方的缓动(t^4);
  5. Quintic:五次方的缓动(t^5);
  6. Sinusoidal:正弦曲线的缓动(sin(t));
  7. Exponential:指数曲线的缓动(2^t);
  8. Circular:圆形曲线的缓动(sqrt(1-t^2));
  9. Elastic:指数衰减的正弦曲线缓动;
  10. Back:超过范围的三次方缓动((s+1)t^3 – st^2);
  11. Bounce:指数衰减的反弹缓动。

每个效果都分三个缓动方式,分别是:

  • easeIn:从0开始加速的缓动,也就是先慢后快;
  • easeOut:减速到0的缓动,也就是先快后慢;
  • easeInOut:前半段从0开始加速,后半段减速到0的缓动。 所有的这些缓动算法都离不开下面4个参数,tbcd,含义如下:
/*
 * t: current time(当前时间);
 * b: beginning value(初始值);
 * c: change in value(变化量);
 * d: duration(持续时间)。
*/

只看上面字面意思其实不好理解,我们套用最简单的匀速直线运动来解释下:

我们都知道,匀速直线运动的公式为

s1 = s2 + vt

结合缓动算法的参数,我们来尝试定义一下Tween.Linear,其实就是匀速直线运动的公式。

s1 = s2 + vt
v = s / t = c / d
s1 = b + (c / d) * t
s1 = b + c*t / d

Tween.Linear = function(t, b, c, d) { 
    return c*t/d + b; 
}

如果我们知道一个元素在每个时刻的位置,那么我们就可以利用requestAnimationFrame实现动画效果

var t = 0, b = 0, c = 1000, d = 10;
var step = function () {
    // value就是当前的位置值
    // 实现右移动画
    DOM.style.right = value + 'px' 
    var value = Tween.Linear(t, b, c, d);
    t = +new Date()
    if (t <= d) {
       // 继续运动
       requestAnimationFrame(step);
    }
};

点赞

1、初始化时,创建一组点赞要跳跃的图片元素,存储起来,但是不挂载,同时创建对应个数的点赞动效对象(容器),保存起来待命
2、当用户触发点赞动作时,开始将点赞要跳跃的图片元素,依次塞进点赞动效容器中,在这个过程中,要判断容器中是否已经有正在执行的跳跃元素,寻找到空的动效容器进行填充并执行动画效果。
3、一般情况下需要跳跃的元素,是一组一组进行填充的,假如我们点赞的速度快了,那么就会出现动效容器不够用的情况,这时候我们就需要动态的添加动效容器。

image.png

import Partical from './partical.js'

class Boom {
  constructor() {}

  boom() {
    let boomNums = 0
    // unAnimateList为未填充的动效元素
    for (const partical of unAnimateList) {
      // this.particalNumbers为一次点赞能触发最大的图片元素
      if (boomNums >= this.allowNumbers) return
      // 在执行填充前再次判断动效容器的状态
      if (partical.animating) {
        continue
      }

      boomNums++
      const r = Math.random()
      // 将动效容器挂载到点赞dom处
      partical.renderIn(this.con)
      // childList为图片元素列表,将图片元素填充到动效容器
      partical.insertChild(childList[Math.floor(r * childListLength)].cloneNode(true))
      // 执行动效
      partical.animate({
        deg: (r * spread + rotate) % 360,
        pow: r * power + 1,
        delay: r * delayRange
      })
    }
    // 如果动效容器不够用,则添加动效容器
    if (boomNums < this.particalNumbers) {
      this.createParticals(this.particalNumbers - boomNums)
    }
  }
}

动效

1、动效是利用补间动画实现的,使用tween.js来计算动效的轨迹。
2、一般情况下点赞动画的轨迹,在x轴方向为匀速直线运动,在y方向上半段为竖直上抛,下半段为自由落体,确定好缓动方式后,再来确定缓动轨迹,点赞大多为抛物线。
3、利用requestAnimationFrame计算出
let r = Math.random()
this.targetZ = Math.round(pow * pow) * (r < 0.5 ? -1 : 1)
this.targetY = Math.round(pow * Math.sin(deg * DEG) * POWER)
this.targetX = Math.round(pow * Math.cos(deg * DEG) * POWER) * (r + 1)

moveX(currentDuration) {
    return Math.tween.Linear(currentDuration, 0, this.targetX,Duration) * 2
}

moveY(currentDuration) {
  let direction = this.direction
  if (direction === 'UP') { // 抛物线
    // 如果是上抛运动
    if (currentDuration < Duration / 2) { // 上半段
      // 上抛过程
      return Quad.easeOut(currentDuration, 0, this.targetY + G, Duration / 2)
    }
    // 下降过程
    return this.targetY + G - Quad.easeIn(currentDuration - Duration / 2, 0, this.targetY / 2, Duration / 2)
  }
  return Quad.easeIn(currentDuration, 0, this.targetY, Duration) // 上升点赞
}

scale(currentDuration) {
  return Quad.easeOut(currentDuration, 1, this.scaleNum, Duration)
}

opacity(currentDuration) {
  return Quad.easeIn(currentDuration, 1, -1, Duration)
}
  
  
let animate = () => {
  let timeGap = +new Date() - StartTimeAfterDelay
  if (timeGap >= 0) {
    if (timeGap > Duration) { // Duration为动效执行的周期
      this.emitEndCB()
      return
    }
    // 动效涉及x方向,y方向,大小,透明度
    this.dom.style.cssText += `;will-change:transform;-webkit-transform:translate3d(${this.moveX(
          timeGap
        )}vh,${this.moveY(timeGap)}vh,0) scale(${this.scale(timeGap)});opacity:${this.opacity(timeGap)};`
    }
  requestAnimationFrame(animate)
}