缓动曲线

缓动曲线在UI动画中的应用十分广泛。缓动曲线可以用来控制动画的运动速率,使其按照我们的意愿模拟真实物体的运动规律。举个例子,当我们往上抛出一个石块时,在不考虑空气阻力的情况下,石块会在重力的作用下,先匀减速上升,直至速度为零。而后,石块的速度又会从零开始匀加速下降。那么,如何模拟这样的运动过程呢?在高中物理课上,我们已经知道,石块的位移和时间之间满足平方关系:
$$
h(t) = v_0t – \frac{1}{2}gt^2 + h_0
$$

其中,$h(t)$ 是石块在 $t$ 时刻的高度,$v_0$ 是石块的初速度,$g$ 是重力加速度,$h_0$ 是石块的初始高度。从这个式子可以看出,我们需要一条二次方的运动曲线(抛物线)来模拟这样的运动过程。而类似这样的一条曲线,就是本文要讨论的缓动曲线。

总的来说,缓动曲线包含四大类,分别是线性(linear)、缓入(ease in)、缓出(ease out)和缓入缓出(ease in and out)。除了线性类,其余三大类又可以细分出各种子类,比如二次方缓动曲线(Quadratic)就是其中的一个子类。以上文提到的石块为例,石块的运动曲线满足二次方缓动曲线,而上升过程满足缓出的过程(速度先快后慢),下降过程则满足缓入过程(速度先慢后快)。本文将对线性类和缓动类的 10 种子类共 11 种类别 31 种组合情况进行公式化整理,并绘制相应的缓动曲线图,供大家查阅。这 11 种类别分别为:

  • Linear
  • Sinusoidal
  • Quadratic
  • Cubic
  • Quartic
  • Quintic
  • Exponential
  • Circular
  • Back
  • Elastic
  • Bounce

缓动曲线的关系

曲线关系

缓动曲线的定义域是 $[0, 1]$,而其值域满足前提

$$
\begin{align}
f(0) &= 0,\newline
f(1) &= 1
\end{align}
$$

从这个前提出发,我们将会看到,不论是直线,还是缓动曲线,都会遵循相同的计算方式。

假设以函数 $f_b(x)$ 作为基础函数(一般可作为缓入函数),若缓入函数表示为

$$f(x) = f_b(x)$$

那么我们将会看到,所有的缓出曲线必然可以表示为

$$
g(x) = 1 – f_b(1-x)
$$

而缓入缓出曲线可以用分段函数表示

$$
h(x) =
\begin{cases}
0.5\cdot f_b(2\cdot x) & ,x \in [0, 0.5] \newline
\newline
0.5\cdot[2-f_b(2\cdot(1-x))] & ,x\in(0.5, 1]
\end{cases}
$$

举例

平方曲线

对于平方曲线,我们定义基础函数为

$$
f_b(x) = x^2,\quad x\in[0, 1]
$$

则缓入函数表示为

$$
f(x) = f_b(x) = x^2,\quad x\in[0, 1]
$$

缓出函数可以表示为

$$
g(x) = 1 – f_b(1-x) = 1-(1-x)^2,\quad x\in[0, 1]
$$

而缓入缓出曲线可以用分段函数表示

$$
\begin{align}
h(x) &=
\begin{cases}
0.5\cdot f_b(2\cdot x) & ,x \in [0, 0.5] \newline
\newline
0.5\cdot[2-f_b(2\cdot(1-x))] & ,x\in(0.5, 1]
\end{cases}\newline
\newline
&=
\begin{cases}
0.5\cdot(2x)^2 & ,x \in [0, 0.5] \newline
\newline
0.5\cdot(2-[2(1-x)]^2) & ,x\in(0.5, 1]
\end{cases}\newline
\newline
&=
\begin{cases}
2x^2 & ,x \in [0, 0.5] \newline
\newline
1-2(1-x)^2 & ,x\in(0.5, 1]
\end{cases}
\end{align}
$$

直线

对于直线来说,上述的缓动曲线关系依然成立。令基础函数为

$$
f_b(x) = x,\quad x\in[0, 1]
$$

则缓入函数:

$$
f(x) = f_b(x) = x,\quad x\in[0, 1]
$$

缓出函数:

$$
g(x) = 1 – f_b(1-x) = 1 – (1 – x) = x,\quad x\in[0, 1]
$$

缓入缓出函数:

$$
\begin{align}
h(x) &=
\begin{cases}
0.5\cdot f_b(2\cdot x) & ,x \in [0, 0.5] \newline
\newline
0.5\cdot[2-f_b(2\cdot(1-x))] & ,x\in(0.5, 1]
\end{cases}\newline
\newline
&=
\begin{cases}
0.5\cdot (2\cdot x) & ,x \in [0, 0.5] \newline
\newline
0.5\cdot[2-(2\cdot(1-x))] & ,x\in(0.5, 1]
\end{cases}\newline
\newline
&=
\begin{cases}
x & ,x \in [0, 0.5] \newline
\newline
x & ,x\in(0.5, 1]
\end{cases}\newline
\newline
&=x,\quad x\in[0, 1]
\end{align}
$$

可见,三种函数都是一样的。这个结果是很容易理解的:既然是直线,那么就没有缓入或缓出的过程,即整个过程都是同样的速率,其导数必然为常数。在这里这个求导的常数就是 1,对应的结果就是

$$
f(x) = g(x) = h(x) = x,\quad x\in[0, 1]
$$

缓动曲线公式和曲线图

Linear

基础函数

$$
f_b(x) = x, \quad x\in[0, 1]
$$

公式

$$
y(x) = x, \quad x\in[0, 1]
$$

曲线图

Sinusoidal

基础函数

$$
f_b(x) = 1-\cos(\frac{\pi}{2}\cdot x), \quad x\in[0, 1]
$$

easeInSine

公式

$$
y(x) = 1-\cos(\frac{\pi}{2}\cdot x), \quad x\in[0, 1]
$$

曲线图

easeOutSine

公式

$$
y(x) = \sin(\frac{\pi}{2}\cdot x), \quad x\in[0, 1]
$$

曲线图

easeInOutSine

公式

$$
y(x) =
\begin{cases}
0.5[1-\cos(\pi\cdot x)] & ,x \in [0, 0.5] \newline
\newline
0.5{\sin[\pi\cdot(x-0.5)]+1} & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

Quadratic

基础函数

$$
f_b(x) = x^2,\quad x\in[0, 1]
$$

easeInQuad

公式

$$
y(x) = x^2,\quad x\in[0, 1]
$$

曲线图

easeOutQuad

公式

$$
y(x) = 1-(1-x)^2,\quad x\in[0, 1]
$$

曲线图

easeInOutQuad

公式

$$
y(x) =
\begin{cases}
2x^2 & ,x \in [0, 0.5] \newline
\newline
1-2(1-x)^2 & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

Cubic

基础函数

$$
f_b(x) = x^3,\quad x\in[0, 1]
$$

easeInCubic

公式

$$
y(x) = x^3,\quad x\in[0, 1]
$$

曲线图

easeOutCubic

公式

$$
y(x) = 1-(1-x)^3,\quad x\in[0, 1]
$$

曲线图

easeInOutCubic

公式

$$
y(x) =
\begin{cases}
4x^3 & ,x \in [0, 0.5] \newline
\newline
1-4(1-x)^3 & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

Quartic

基础函数

$$
f_b(x) = x^4,\quad x\in[0, 1]
$$

easeInQuart

公式

$$
y(x) = x^4,\quad x\in[0, 1]
$$

曲线图

easeOutQuart

公式

$$
y(x) = 1-(1-x)^4,\quad x\in[0, 1]
$$

曲线图

easeInOutQuart

公式

$$
y(x) =
\begin{cases}
8x^4 & ,x \in [0, 0.5] \newline
\newline
1-8(1-x)^4 & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

Quintic

基础函数

$$
f_b(x) = x^5,\quad x\in[0, 1]
$$

easeInQuint

公式

$$
y(x) = x^5,\quad x\in[0, 1]
$$

曲线图

easeOutQuint

公式

$$
y(x) = 1-(1-x)^5,\quad x\in[0, 1]
$$

曲线图

easeInOutQuint

公式

$$
y(x) =
\begin{cases}
16x^5 & ,x \in [0, 0.5] \newline
\newline
1-16(1-x)^5 & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

Exponential

基础函数

$$
f_b(x) = 2^{10\cdot(x – 1)},\quad x\in[0, 1]
$$

easeInExpo

公式

$$
y(x) = 2^{10\cdot(x – 1)},\quad x\in[0, 1]
$$

曲线图

easeOutExpo

公式

$$
y(x) = 1 – 2^{-10x},\quad x\in[0, 1]
$$

曲线图

easeInOutExpo

公式

$$
y(x) =
\begin{cases}
2^{10\cdot(2 x – 1) – 1} & ,x \in [0, 0.5] \newline
\newline
1 – 2^{-10\cdot(2 x – 1) – 1} & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

Circular

基础函数

$$
f_b(x) = 1 – \sqrt{1 – x^2},\quad x\in[0, 1]
$$

easeInCirc

公式

$$
y(x) = 1 – \sqrt{1 – x^2},\quad x\in[0, 1]
$$

曲线图

easeOutCirc

公式

$$
y(x) = \sqrt{1 – (1 – x)^2},\quad x\in[0, 1]
$$

曲线图

easeInOutCirc

公式

$$
y(x) =
\begin{cases}
0.5\cdot(1 – \sqrt{1 – 4 x^2}) & ,x \in [0, 0.5] \newline
\newline
0.5\cdot(1 + \sqrt{1 – 4\cdot(1 – x)^2}) & ,x\in(0.5, 1]
\end{cases}
$$

曲线图


接下来的Back和Elastic缓动曲线,需要更多的参数辅助控制曲线的形态。

Back

基础函数

$$
f_b(x) = x^2\cdot[(1 + n)\cdot x – n],\quad x\in[0, 1]
$$

easeInBack

公式

$$
y(x) = x^2\cdot[(1 + n)\cdot x – n],\quad x\in[0, 1]
$$

曲线图
(n = 1.5)

(n = 2.5)

easeOutBack

公式

$$
y(x) = 1 – (1 – x)^2\cdot[(1 + n)\cdot(1 – x) – n],\quad x\in[0, 1]
$$

曲线图

(n = 1.5)

(n = 2.5)

easeInOutBack

公式

$$
y(x) =
\begin{cases}
2x^2\cdot[(1 + n)\cdot 2 x – n] & ,x \in [0, 0.5] \newline
\newline
1 – 2 (1 – x)^2\cdot[(1 + n)\cdot 2 (1 – x) – n] & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

(n = 1.5)

(n = 2.5)

Elastic

基础函数

$$
f_b(x) = 2^{10(x – 1)}\cdot\sin{[(2 n + \frac{1}{2})\pi\cdot x]},\quad x\in[0, 1]
$$

easeInElastic

公式

$$
y(x) = 2^{10(x – 1)}\cdot\sin{[(2 n + \frac{1}{2})\pi\cdot x]},\quad x\in[0, 1]
$$

曲线图

(n = 3)

(n = 5)

(n = 7)

easeOutElastic

公式

$$
y(x) = 1 – 2^{10 (-x)}\cdot\sin{[(2 n + \frac{1}{2})\pi\cdot (1 – x)]},\quad x\in[0, 1]
$$

曲线图

(n = 3)

(n = 5)

(n = 7)

easeInOutElastic

公式

$$
y(x) =
\begin{cases}
0.5\cdot 2^{10\cdot(2 x – 1)}\cdot\sin{[(2 n + \frac{1}{2})\pi\cdot(2x)]} & ,x \in [0, 0.5] \newline
\newline
0.5\cdot{2-2^{10\cdot[2 (1 – x) – 1]}\cdot\sin{[(2 n + \frac{1}{2})\pi\cdot 2(1 – x)]}} & ,x\in(0.5, 1]
\end{cases}
$$

曲线图

(n = 3)

(n = 5)

(n = 7)


Bounce

要处理 Bounce 曲线,我们需要更多的技巧。首先 Bounce 曲线处处连续,但并非处处可导,不可导的点处在 Bounce 的各个转折点。要构造这样的一个函数,我们就需要一定的技巧。在前面的例子中,基础函数都可以用一个直观的函数表达式来表示。在 Back 和 Elastic 曲线中,我们需要引入更多的参数来控制曲线的形态。而在 Bounce 曲线中,我们也需要两个参数来控制曲线的形态。所不同的是,这两个参数是需要根据自变量的值动态调整的。因此我们需要一个广义的函数。可以借助 C 语言来定义这个基础函数

float bounce(float progress)
{
    for(float a=0, b=1; 1; a+=b,b/=2.0f) {
        if (progress >= (7 - 4.0f * a) / 11.0f) {
            return -powf((11-6*a-11*progress)/4, 2) + powf(b, 2);
        }
    }
}

进一步,我们需要定义 easeIn, easeOut 和 easeInOut 函数。这三个函数通过接收基础函数和给定的自变量值来获取函数值。我们可以通过函数指针来实现

typedef float (*curve)(float);

有了函数指针,我们的缓动函数就可以声明如下:

float easeIn(curve cv, float x);
float easeOut(curve cv, float x);
float easeInOut(curve cv, float x);

使用的时候,把 bounce 函数传入对应的缓动函数中即可。

easeInBounce

函数:

float easeIn(curve cv, float x)
{
    return cv(x);
}

曲线图:

easeOutBounce

函数:

float easeOut(curve cv, float x)
{
    return 1-cv(1-x);
}

曲线图:

easeInOutBounce

函数:

float easeInOut(curve cv, float x)
{
    if (x <= 0.5f)
        return 0.5f * cv(2.0f * x);
    else
        return (2.0f - cv(2.0f * (1.0f - x))) / 2.0f;
}

曲线图:

外部链接

留下评论