# 大前端动画

> 大前端开发中经常会遇到动画的开发，那么什么是动画？在物理学中运动就是研究物体在时间维度和空间维度上改变的现象，所以动画也一样，动画主要研究2个因素，发生运动物体的**时间**和**空间**。

#### Web前端开发中的动画

在 Web 前端开发中实现动画有2种方式。要么依靠 CSS 实现动画，要么依靠 JS 控制实现动画。

##### CSS 实现动画
首先要说 CSS 中的4个概念：animation、transition、transform、translate

| 属性 | 含义 |
| :---: | :---: |
| transition\(过度动画\) | 用于设置元素的样式过渡效果，和 animation 有类似的效果，但存在使用场合有着较大差别 |
| transform\(变形\) | 用于设置元素的旋转、位移、缩放。和设置元素的动画并没直接关系，就跟写 css 属性一样 |
| translate\(移动\) | 用于设置元素的位置，就是 transform 的一个属性 |
| animation（动画） | 用于设置怨毒的动画属性，它是一个简写，有6个属性值 |

**transition**
字面意思，过渡是指元素从属性a的某个值过渡到属性a的另一个值，这就是一个状态的改变，但是需要一个条件来触发从而发生这种转变，比如 `&:hover,&:checked,&:focus、媒体查询或者 JS`

```
#box {
  height: 100px;
  width: 100px;
  background: green;
  transition: transform 1s ease-in 1s;
}
#box:hover{
  transform: rotate(180deg) scale(0.5,0.5);
  transform: translateX(100px);
  transform: translateY(100px) translateX(100px) scale(0.5, 0.5);
}
<div id="box"></div>
```

分析：给 div 添加了一个过渡动画，动画指定了 transform 动画，触发时机为当鼠标移上去的时候。因此当鼠标移入的时候元素的 transform 属性发生变化，那么这个时候触发了 transition 动画，当鼠标移除的时候也产生了 transform 的变化，因此还是会触发 transition，产生动画。
上面设置了3个 transform 只有最后一个生效
因此 transition 产生动画的条件是设置的 property 发生变化，这种动画的特点是需要一个驱动力去触发。因此就存在一些缺点：
1. 需要事件触发，没法在网页加载时自动发生
2. 是一次性的，不能重复发生，除非再次触发
3. 只可以定义开始状态和结束状态，不能定义中间状态，因此没有丰富的动画空间
4. 一条 transition 规则，只能定义一个属性的变化，不能涉及多个属性

语法：**transition：property duration timing-function delay**

| 属性 |含义 |
| :---: | :---: |
| transition-property | 规定设置过渡效果的 css 属性名称 |
| transition-duration | 规定完成过渡效果需要时间 |
| transition-timing-function | 规定速度效果的速度曲线 |
| transition-delay | 规定动画效果何时开始 |


**animation**

animation 总体来说是对 transition 的增强，不再受限于触发时机和动画的属性值。

```
.box {
  height: 100px;
  width: 100px;
  border: 15px solid black;
  animation: changebox 4s ease-in-out 1s 1 alternate running forwards;
}
.box:hover {
  animation-play-state: paused;
}
@keyframes changebox {
  10% {
    background: red;
  }
  50% {
    width: 80px;
  }
  70% {
    border: 15px solid yellow;
  }
  100% {
    width: 180px;
    height: 180px;
  }
}
<div class="box"></div>
```

**animation: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, animation-fill-mode**



|属性 | 含义 |
| :----: | :----: |
| animation-name | 用来调用@keyframes定义好的动画，与@keyframes定义的动画名称一致 |
| animation-duration | 指定元素播放动画所持续的时间 |
| animation-timing-function | 指定速度效果的速度曲线，是针对每一个小动画所在时间范围内的变化频率|
| animation-delay| 定义在执行动画之前的等待时间|
| animation-iteration-count| 定义动画的播放次数，可选具体次数或者无限次（infinite）|
| animation-direction |设置动画播放方向：normal（按时间轴顺序）、alternate（轮流，即来回往复进行) |
| animation-play-state| 控制元素的播放状态：running（继续）、paused（暂停）|
| animation-fill-mode | 控制动画结束后元素的样式，有4个值： none（回到动画之前的状态）、forwards（元素停留在动画结束后的状态）、backwords（动画回到第一帧的状态）、both（根据 animation-direction 轮流应用 forwards 和 backwords 规则）。注意与 iteration-count 不要冲突|


总结：单个动画效果、简单的由 transtion 实现，复杂的用 animation 实现。animation 出现后市面上出现了很多这种 css 动画库，其中我在使用 animate.css 推荐小伙伴们使用下

##### JS 实现动画
大家用 JS 写动画立马想到的是 setTimeout 和 setInterval，但是较好的动画体验是保持在 60fps 最好，上面的2个 api 由于会受到 runloop 的影响，并不会特别准时，JS 有个 requestAnimationFrame api 可以保持动画在 60fps，
JS 实现动画的本质就是控制元素在时间和空间上的变化的研究。

假如要实现一个匀速直线动画，让一个 div 在 3秒内在水平方向上从向由右移动500px。那么如何实现，先从物理问题上解决吧。

总时间： 3s
总位移： 500px
那么每秒移动多少 500px/3s
我们设计一个 JS 函数。4个参数， 属性开始值，属性结束值，动画执行时间，回调函数

大体思路是：外界传入上面4个参数，我们可以记录函数调用刚开始的时刻也就是开始时间（start），然后通过 performance.now\(\) 拿到当前时间（now），然后 period = \(now-start\) 就是经过的时间。然后通过 period/time 就是时间的进度百分比，拿这个百分比再去乘以总的属性值差就是当前的属性值，然后将计算结果实时调用回调函数（这个回调函数就是指定这个属性值如何应用到动画元素上）

```
/**
* 执行补间动画方法
*
* @param {Number} start 开始数值
* @param {Number} end 结束数值
* @param {Number} time 补间时间
* @param {Function} callback 每帧回调
* @param {Function} timing 速度曲线，默认匀速
*/
function animate(start, end, time, callback, timing = t => t) {
  let startTime = performance.now() // 设置开始的时间戳
  let period = end - start // 拿到数值差值
  // 创建每帧之前要执行的函数
  function loop() {
    liveAnimationFunction = requestAnimationFrame(loop) // 下一调用每帧之前要执行的函数
    const passTime = performance.now() - startTime // 获取当前时间和开始时间差
    let per = passTime / time // 计算当前已过百分比
    if (per >= 1) { // 判读如果已经执行
      per = 1 // 设置为最后的状态
      cancelAnimationFrame(raf) // 停掉动画
    }
    const pass = period * timing(per) // 通过已过时间百分比*开始结束数值差得出当前的数值
    callback(pass)
  }
  let liveAnimationFunction = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数
}

function doMove(easing) {
  asing])
}

function move(box, value) {
  box.style.transform = `translateX(${value}px)`
}
```

#### Native 端动画（iOS为例）

其实动画的本质就是元素时间和空间上发生变化的研究。在 web 前端如此，在 native 端也是如此，不过就是换了一些 api 如此。

举个例子，就拿上面所说的水平位移为例，下面给 iOS 的原生代码

```
//CALayer 层动画
CABasicAnimation *positionAnimation = [CABasicAnimation animation];
//指定动画路径是水平方向x轴
positionAnimation.keyPath = @"position.x";
//指定位移距离
positionAnimation.toValue = @1000;
//下面2行代码让动画停留在动画结束的位置
positionAnimation.fillMode = kCAFillModeForwards;//（效果完全等同于 css 中的 animation-fill-mode属性）
positionAnimation.removedOnCompletion = NO;
[self.animationView.layer addAnimation:positionAnimation forKey:nil];
```

css 中的 animation-fill-mode（控制动画结束后元素的样式，有4个值： none（回到动画之前的状态）、forwards（元素停留在动画结束后的状态）、backwords（动画回到第一帧的状态）、both（根据 animation-direction 轮流应用 forwards 和 backwords 规则））和 native（iOS）端的 fillMode 属性一致。

下面提出 iOS 端的 fillMode 取值选项
```
/* `fillMode' options. */
CA_EXTERN CAMediaTimingFillMode const kCAFillModeForwards
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBackwards
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBoth
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeRemoved
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
```

看得出来 fillMode web 端和 native 端是一模一样的。

再举个例子，平时我们有可能画一根横线，在 web 端 和 native 端都存在 view 这样的概念，在 web 端可能会是一个 div（高度设置为1）或者是 canvas 实现（canvas 拿到当前上下文、绘制路径、关闭路径、填充颜色）。在 native 端也一样，开启绘图上下文、拿到上下对象、绘制路径、上色、关闭上下文。

所以其他具体的例子也就不举了，本质上大前端的所有动画干的事情都一样，所以我们需要处理好时间和位置的关系。
