大前端开发中经常会遇到动画的开发,那么什么是动画?在物理学中运动就是研究物体在时间维度和空间维度上改变的现象,所以动画也一样,动画主要研究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 发生变化,这种动画的特点是需要一个驱动力去触发。因此就存在一些缺点:
- 需要事件触发,没法在网页加载时自动发生
- 是一次性的,不能重复发生,除非再次触发
- 只可以定义开始状态和结束状态,不能定义中间状态,因此没有丰富的动画空间
- 一条 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 端也一样,开启绘图上下文、拿到上下对象、绘制路径、上色、关闭上下文。
所以其他具体的例子也就不举了,本质上大前端的所有动画干的事情都一样,所以我们需要处理好时间和位置的关系。