mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 20:00:40 +00:00
194 lines
9.5 KiB
Markdown
194 lines
9.5 KiB
Markdown
# 大前端动画
|
||
|
||
> 大前端开发中经常会遇到动画的开发,那么什么是动画?在物理学中运动就是研究物体在时间维度和空间维度上改变的现象,所以动画也一样,动画主要研究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 端也一样,开启绘图上下文、拿到上下对象、绘制路径、上色、关闭上下文。
|
||
|
||
所以其他具体的例子也就不举了,本质上大前端的所有动画干的事情都一样,所以我们需要处理好时间和位置的关系。
|