前端动画实现-1

动画的基本原理

什么是动画?

动画是通过快速连续排列批次差异极小的连续图像来制造运动错觉和变化错觉的过程 ——维基百科

满足四个条件:

  • 快速
  • 连续排列
  • 彼此差异极小
  • 制造错觉

动画的发展

常见的前端动画技术:Sprite 动画、CSS 动画、JS 动画、SVG 动画、WebGL 动画

按应用分类:UI 动画、基于 Web 的游戏动画和动画数据可视化

计算机动画原理

计算机图形学:

计算机视觉的基础,涵盖点、线、面、体、场的数学构造方法。

  • 几何和图形数据的输入、存储和压缩。
  • 描述纹理、曲线、光影等算法。
  • 物体图形的数据输出(图形接口、动画技术),硬件和图形的交互技术。
  • 图形开发软件的相关技术标准。

计算机动画:

计算机图形学的分支,主要包含 2D、3D 动画。

无论动画多么简单,始终需要定义两个基本状态,即开始状态和结束状态。没有它们,我们将无法定义插值状态,从而填补了两者之间的空白。

帧:连续变换的多张画面,其中的每一幅画面都是一帧。

帧率:用于度量一定时间段内的帧数,通常的测量单位是 FPS (frame per second) 。

帧率与人眼:一般每秒 10-12 帧人会认为画面是连贯的,这个现象称为视觉暂留。对于一些电脑动画和游戏来说低于 30FPS 会感受到明显卡顿,目前主流的屏幕、显卡输出为 60FPS,效果会明显更流畅。

空白的补全方式有以下两种

  • 补间动画: 传统动画,主画师绘制关键帧,交给清稿部门,清稿部门的补间动画师补充关键帧进行交付。(类比到这里,补间动画师由浏览器来担任,如 keyframe,transition)
  • 逐帧动画(Frame By Frame) : 从词语来说意味着全片每一帧逐帧都是纯手绘。(如 CSS 的 steps 实现精灵动画)

前端动画分类

CSS 动画

CSS animation

  • animation-name : 指定应用的一系列动画
  • animation-duration : 制定一个动画周期的时长
  • animation-timing-function : 定义 CSS 动画在每一动画周期中执行的节奏
  • animation-delay : 定义动画于何时开始
  • animation-iteration-count : 定义动画在结束前运行的次数
  • animation-direction : 定义动画是否反向播放
  • animation-fill-mode : 设置 CSS 动画在执行之前和之后如何将样式应用于目标
  • animation-play-state : 定义一个动画是否运行或暂停

transform

translate : 平移
scale : 缩放
rotate : 旋转
skew : 倾斜

transition API

transition-property : 指定哪个或者哪些 CSS 属性用于过渡
transition-duration : 指定过度的时长
transition-timing-function : 指定一个函数,定义属性值怎么变化
transition-delay : 指定延迟

关键帧 @Keyframe

可以使用:from to、百分比

优点 : 简单、高效 声明式的 不依赖于主线程,采用硬件加速(GPU) 简单的控制 keyframe animation 的播放盒暂停

缺点 : 不能动态修改或定义动画内容、不同的动画无法实现同步、多个动画无法堆叠

适用场景 : 简单的 h5 活动/宣传页

补充

推荐库 : animation.css、shake.css、motioncanvas

CSS 补间动画使用 Transition API 和 Keyframe 实现

CSS 逐帧动画使用 Animation API 中的 steps 实现

SVG 动画

svg 是基于 XML 的矢量图形描述语言,它可以与 CSS 和 S 较好的配合,实现 svg 动画通常有三种方式:SMIL、JS、CSS

我们经常使用 animation, transform, transition 来实现 svg 动画,它比 JS 更加简单方便。

优点:通过矢量元素实现动画,不同的屏幕下均可获得较好的清晰度。可以实现一些特殊的效果,如:描字,形变,墨水扩散等。

缺点:使用方式较为复杂,过多使用可能会带来性能问题。

简单的例子:

  • 通过 js 修改 filter/optaicy 等属性实现“溶解”效果
  • 通过 svg 的 stroke-dashoffset 和 stroke-dasharray 实现画笔效果。
    • stroke-dasharray 可控制用来描边的点划线的图案范式。它是一个数列,数与数之间用逗号或者空白隔开,指定短划线和缺口的长度。如果提供了奇数个值,则这个值的数列重复一次,从而变成偶数个值。因此,5,3,2 等同于 5,3,2,5,3,2。
    • stroke-dashoffset 属性指定了 dash 模式到路径开始的距离。

画笔效果

stroke-dasharray:可以理解为虚线的产生,比如下面的效果就是 5px 的实线、5px 的空白交替虚线 如果是奇数元素就会自动重复一遍,会变成长度不一的虚线段

1
2
3
<svg>
<line stroke-dasharray="5, 5" />
</svg>

stroke-dashoffset:偏移量,就是相对于绘制起点的偏移量,偏移量不会大于线段总长度 比如设置为-20,就相当于向右移动了 20px。这个也是画线的核心实现属性。 画线效果就是让偏移量从 0 到线段的长度,长度可以用 path 属性的 getTotalLength()函数获取 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#path {
animation: move 3s linear forwards;
stroke-dasharray: 511px, 511px;
}

@keyframes move {
0% {
stroke-dashoffset: 511px;
}
100% {
stroke-dashoffset: 0;
}
}

这个效果原理:最初线条分为 511px 实线,和 511px 空隙,但是由于设置了 offset 使线条偏移不可见了,当不断修改 offset 后,线条慢慢出现。

JS 动画

自己封装一个动画函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function animate({ easing, draw, duration }) {
let start = performance.now() //开始时间,使用perfromance更精确
return new Promise((resolve) => {
//便于拿到动画的终态(resolve)
requestAnimationFrame(function animate(time) {
//time表示当前执行时间
let timeFraction = (time - start) / duration //timeFraction是一个0-1的进度
if (timeFraction > 1) timeFraction = 1 //表示结束,则令为1并结束
let progress = easing(timeFraction) //通过缓动函数改变到真实进度
draw(progress) //根据进度绘制
if (timeFraction < 1) {
requestAnimationFrame(animate)
} else {
resolve()
}
})
})
}

核心参数:

  • easing:缓动函数,是物理运动的核心参数,
  • draw:绘制函数,绘制具体的图形,利用 dom.style.transfrom 做主要变化
  • duration:持续时间

调用的时候分别传入三个参数:

1
2
3
4
5
6
7
8
9
animate({
easing(timeFraction) {
return timeFraction;
},
draw(progress) {
elem.style.transform = `translate(${progress}px,0)`;
}
duration: 1000,
})

easing

可以模拟一些物理过程,比如匀速、匀加速、平抛等等 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function easing(timeFraction) {
// 重力加速
return 0.5 * 9.8 * timeFraction ** 2
}
function easing(timeFractionX, timeFractionY) {
// 平抛
return {
x: timeFractionX,
y: timeFractionY ** 2,
}
}
function easing(timeFraction) {
// 拉弓 x=∫v⋅dt=−v0t+1/2at^2
return {
x: timeFraction,
y: -2 * timeFraction - timeFraction ** 2, // v0 = 2,a = 2
}
}
// 三次贝塞尔曲线B(t)=(1−t)^3 * P0 + 3t * (1−t)^2 * P1 + 3t^2 * (1−t) P2+ t^3 * P3,t∈[0,1]
const bezierPath = (x1, y1, x2, y2, t) => {
const x = 3 * t * (1 - t) ** 2 * x1 + 3 * t ** 2 * (1 - t) * x2 + t ** 3 * 1
const y = 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3 * 1
return [x, y]
}

复杂动画

弹跳小球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
async function autoDamping() {
let damping = 0.7, // 衰减系数
duration = 1000,
height = 300

// 当衰减到一定边界值时停止动画
while (height > 1) {
const down = (progress) => {
ball.style.transform = `translate(0, ${height * (progress - 1)}px)`
}
await animate({
duration: duration,
easing(t) {
return t ** 2
},
draw: down,
})
height *= damping ** 2 // ** 2 可以使动画效果更加柔和
duration *= damping
const up = (progress) => {
ball.style.transform = `translate(0, ${-height * progress}px)`
}
await animate({
duration: duration,
easing(t) {
return 2 * t - t ** 2
},
draw: down,
})
}
}

椭圆运动,公式:x^2 / a^2 + y^2 / b^2 = 1, x = a * cos(a), y = b *sin(a)

1
2
3
4
5
6
7
8
9
10
11
12
13
const draw = (progress) => {
const x = 150 * Math.cos(Math.PI * 2 * progress)
const y = 100 * Math.sin(Math.PI * 2 * progress)
ball.style.transform = `translate(${x}px, ${y}px)`
}

animate({
duration: 2000,
easing(t) {
return 2 * t - t ** 2
},
draw,
})

为什么使用performance.now()而非Date.now()?为什么使用requestAnimationFrame而非setTimeout或者setInterval:

  • performance.now()会以恒定速度自增,精确到微妙级别,不易被篡改.
  • requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的的 cpu,gpu 和内存使用量。

相关推荐

动画资源

动画代码示例:

  • codepen.com
  • codesandbox.com

设计网站:

  • dribbble.com

动画制作工具(一般都是 UE、UI 同学使用):

  • 2D:Animate CC、After Effects
  • 3D:Cinema 4D、Blender、Autodesk Maya

svg :

  • Snap.svg - 现代 SVG 图形的 JavaScript 库。
  • Svg.js - 用于操作和动画 SVG 的轻量级库。

js :

  • GSAP - JavaScript 动画库。
  • TweenJs - 一个简单但功能强大的 JavaScript 补间/动画库。CreateJS 库套件的一部分。
  • Velocity - 加速的 JavaScript 动画。

css :

  • Animate.css - CSS 动画的跨浏览器库。像一件简单的事情一样容易使用。

  • canvas :

  • EaselJs - EaselJS 是一个用于在 HTML5 中构建高性能交互式 2D 内容的库

  • Fabric.js - 支持动画的 JavaScript 画布库。

  • Paper.js - 矢量图形脚本的瑞士军刀 - Scriptographer 使用 HTML5Canvas 移植到 JavaScript 和浏览器。

  • Pixijs - 使用最快、最灵活的 2D WebGL 渲染器创建精美的数字内容。