前端动画实现

动画的基本原理

什么是动画?

  • 动画是通过快速、连续排列、彼此差异极小的、制造错觉的过程

常见的前端动画技术

  • Sprite动画、CSS动画、JS动画、SVG动画和WebGL动画

计算机动画概念

计算机图形学:

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

计算机动画原理

  • 动画多么简单都需要定义两个基本状态,也就是开始状态和结束状态。没有了两个状态就不可以填补两者之间的空白,做不出动画效果。

帧和帧率

  • 帧:连续百年换的多张画面,其中的每一幅画面都是一帧

  • 帧率:用于度量一定时间段内的帧数,通常的测量单位是FPS [一秒x张]

    • 帧率和人眼:人眼10-12FPS会认为画面是连贯的,因为视觉暂留现象;但对于游戏而言,30FPS会感到明显卡顿,需要60FPS

空白的补全方式

  • 补间动画

    • 类比浏览器[keyframe, transition]
  • 逐帧动画

    • CSS的Step的逐帧动画实现

前端动画分类

CSS动画

定义

  • 是一种样式表语言,描述HTML或者XML类的语言

  • 常用的实现方式:CSS animation

    • animation-name指定应用的一系列动画,每个名称代表一个由keyframes定义的动画序列
    • animation-duration指定一个动画周期的时长
    • animation-timing-function属性定义CSS动画在每一个动画周期中执行的节奏
    • animation-delay属性定义了延时多少时间开始
    • animation-iteration-count属性定义了在结束前运行的次数
    • animation-direction属性指示了动画是否反向播放
    • animation-fill-mode属性指示了动画在执行之前和之后如何将样式应用于其目标
    • animation-play-state属性定义一个动画是否运行或者暂停
  • CSS的形体变换 [Transform]:只能转换由盒模型定位的元素

    • transform-origin:指定原点的位置默认值为元素中心,可以被移动。可以使用该属性进行旋转、缩放和倾斜
      • translate:移动【指定位移长度{纵向和横向两个}】
      • scale:缩放【你需要指定缩放比例{有横向比例和纵向比例,都需要声明}和缩放中心】
      • skew:倾斜【你需要指定倾斜角度{有水平方向的倾斜角度和垂直方向的倾斜角度}】
    • Transition API在dom加载完成或者class发生变化的时候触发
  • keyframe实现 [关键帧法]

    • 和transition相比,该方法可以控制动画序列的中间步骤

优点与缺点

  • 优点:简单、高效,声明式,不依赖主线程,采用GPU简单的控制keyframe、animation的播放和暂停

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

使用场景

  • 简单的h5宣传页

  • 推荐库:animation.css和shake.css

svg动画

定义

svg是基于XML的矢量图形来描述语言,可以很好的和CSS和JS较好的配合,实现方式有SMIL\JS\CSS

优点与缺点

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

  • 缺点:使用复杂,容易带来性能问题

JS动画

JS可以实现复杂的动画,也可以操作canvas动画API上来进行绘制

优点与缺点

  • 优点:

    • 灵活,可以调节若干函数,参数更灵活
    • 可以做的比CSS的keyframe的粒度更细
    • 可以做多个状态的转化,这在css中是很难做到的
  • 缺点:

    • 调优方式比CSS复杂
    • 对于性能较差的浏览器,CSS可以做到优雅降级,而JS必须得兼容

总结

  • UI元素采用较小的独立状态的时候使用CSS

  • 需要对动画进行大量控制的时候使用JS

  • 在特定场景下可以使用SVG,使用CSS和JS进行对SVG的操作

前端动画实现

我们在这里主要阐述偏JS的动画

动画函数

  • draw: 绘制函数

    可以把draw想象成一直画笔,随着函数的执行,这个画笔的函数回反复被调用,并传入当前持续进度progress,progress是一个介于0-1之间的数字

  • easing: 缓动函数
    缓动函数决定了时间在线性增长过程中,实际的执行进度的变化,是一个表达式

  • duration: 持续时间
    动画的持续时间,单位是毫秒

  • requestAnimationFrame: 浏览器重绘时开始做,与浏览器的重绘保持一致,如果使用setTimeout可能会有该现象:浏览器重绘较慢,导致setTimeout不同步执行导致丢帧现象

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function animate ({easing, draw, duration}) {
    let start = performance.now()
    return new Promise(resolve => {
    requestAnimationFrame(function animate (time) {
    let timeFraction = (time - start) / duration
    if (timeFraction > 1) timeFraction = 1

    let progress = easing(timeFraction)

    draw(progress)

    if (timeFration < 1) {
    requestAnimationFrame(animate)
    } else {
    resolve()
    }
    })
    })
    }

JS执行动画的核心思想

$$
\Delta r = \Delta v \Delta t
$$

简单动画

匀速运动

1
2
3
4
5
6
7
8
9
10
11
12
13
const ball = document.querySelector('.ball')

const draw = (progress) => {
ball.style.transform = `translate(${progress}px, 0)`
}

animate({
duration: 1000,
easing(timeFraction){
return timeFraction * 100
},
draw
})

重力

1
2
3
4
5
6
7
8
9
10
11
12
13
const ball = document.querySelector('.ball')

const draw = (progress) => {
ball.style.transform = `translate(0, ${500 * (progress - 1)}px)`
}

animate({
duration: 1000,
easing(timeFraction){
return timeFraction ** 2
}
draw
})

摩擦力

1
2
3
4
5
6
7
8
9
10
11
12
13
const ball = document.querySelector('.ball')

const draw = (progress) => {
ball.style.transform = `translate(0, ${500 * (progress - 1)}px)`
}

animate({
duration: 1000,
easing(timeFraction){
return timeFraction * (2 - timeFraction)
}
draw
})

平抛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ball = document.querySelector('.ball')

const draw = (progress) => {
ball.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px)`
}

animate({
duration: 1000,
easing(timeFraction){
return {
x: timeFraction,
y: timeFraction ** 2
}
}
draw
})

拉弓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const ball = document.querySelector('.ball')

const back = (x, timeFraction) => {
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
}

const draw = (progress) => {
ball.style.transform = `translate(${200 * progress.x}px, ${-500 * progress.y}px)`
}

animate({
duration: 1000,
easing(timeFraction){
return {
x: timeFraction,
y: back(2, timeFraction)
}
}
draw
})

贝塞尔曲线

$$
\Beta(t) =\Rho_{0}(1-t)^3+3\Rho_{1}t(1-t)^2+3\Rho_{2}t^2(1-t)+\Rho_{3}t^3 ,t\in[0,1]
$$

用专门的网站来找出点来代入贝塞尔曲线

弹跳小球

  • 使用缓动函数

  • 使用自动衰减[更推荐使用]

椭圆运动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ball = document.querySelector('.ball')

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: 1000,
easing(timeFraction){
return {
x: timeFraction,
y: timeFraction ** 2
}
}
draw
})

相关实践

  • Lottie:解析AE动画