56

动画上下文中的缓动函数是什么意思。看来dojo、jquery、silverlight、flex等UI系统都有缓动功能的概念。我找不到缓动函数的一个很好的解释?谁能解释缓动函数的概念,或者对它们进行一个很好的解释,我对这个概念感兴趣,而不是对框架的具体细节感兴趣?

缓动是严格用于位置还是通用的并且可以应用于对象的任何属性?

4

5 回答 5

129

缓动函数通常是描述给定完整百分比的属性值的函数。不同的框架使用略有不同的变体,但是一旦你明白了这个概念就很容易掌握,但最好看几个例子。

首先让我们看一下我们所有的缓动函数都会遵守的接口。

我们的缓动函数将接受几个参数:

  • 完成百分比:(0.01.0)。
  • elapsedTime:动画已经运行的毫秒数
  • startValue:开始的值(或完成百分比为 0% 时的值)
  • endValue:结束的值(或完成百分比为 100% 时的值)
  • totalDuration:动画所需的总长度(以毫秒为单位)

并将返回一个数字,该数字表示属性应设置的值。

注意:这与 jQuery 用于其缓动函数的签名相同,我将借用它作为示例。

最容易理解的是线性缓动:

var linear = function(percent,elapsed,start,end,total) {
    return start+(end-start)*percent;
}

现在使用它:

假设我们有一个动画将持续 1000 毫秒,并且应该从 0 开始并在 50 结束。将这些值传递给我们的缓动函数应该告诉我们实际值应该是什么:

linear(0, 0, 0,50, 1000)        // 0
linear(0.25, 250, 0, 50, 1000)  // 12.5
linear(0.5, 500, 0, 50, 1000)   // 25
linear(0.75, 750, 0, 50, 1000)  // 37.5
linear(1.0, 1000, 0, 50, 1000)  // 50

这是一个非常直接(没有双关语)的补间。这是一个简单的线性插值。如果您要绘制价值与时间的关系图,那将是一条直线:

线性缓动

让我们看一个更复杂的缓动函数,一个二次缓动函数:

var easeInQuad = function (x, t, b, c, d) {
    return c*(t/=d)*t + b;
}

让我们看看相同的结果,使用与以前相同的输入:

easeInQuad(0, 0, 0, 50, 1000)      // 0
easeInQuad(0.25, 250, 0, 50, 1000) // 3.125
easeInQuad(0.5, 500, 0, 50, 1000)  // 12.5
easeInQuad(0.75, 750, 0, 50, 1000) // 28.125
easeInQuad(1, 1000, 0, 50, 1000)   // 50

请注意,这些值与我们的线性缓动非常不同。它开始时非常缓慢,然后加速到终点。在动画完成 50% 时,它仅达到 12.5 的值,这是我们指定的start和值之间实际距离的四分之一。end

如果我们要绘制这个函数,它看起来像这样:

四重缓入

现在让我们看一个基本的缓出:

var easeOutQuad = function (x, t, b, c, d) {
    return -c *(t/=d)*(t-2) + b;
};

这本质上是缓入的“相反”加速曲线。它开始很快,然后减速到其结束值:

缓出

还有一些功能可以轻松进出:

var easeInOutQuad = function (x, t, b, c, d) {
    if ((t/=d/2) < 1) return c/2*t*t + b;
    return -c/2 * ((--t)*(t-2) - 1) + b;
};

缓入出

此功能将开始缓慢并缓慢结束,在中间达到其最大速度。

您可以使用许多缓动/插值:线性、二次方、三次方、夸脱、五元组、正弦。还有一些特殊的缓动函数,如 Bounce 和 elastic,它们都有自己的。

例如,弹性缓动:

var easeInElastic = function (x, t, b, c, d) {
    var s=1.70158;var p=0;var a=c;
    if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
    if (a < Math.abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*Math.PI) * Math.asin (c/a);
    return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},

弹性缓和

也许其他人可以解释插值背后的实际数学部分,因为老实说,我不是数学专家。但这就是缓动函数本身的基本原理。

当您开始补间/动画时,动画引擎会记住您想要的开始和结束值。然后每次更新时,它都会计算出已经过去了多少时间。它使用值调用提供的缓动函数来确定属性应该设置的值。只要所有的缓动函数实现相同的签名,它们就可以轻松地被替换掉,并且核心动画引擎不必知道区别。(这使得关注点分离非常好)。

你会注意到我已经避免明确地谈论xand位置,因为 easing 与 position本身y没有任何特别的关系。缓动函数只定义了开始值和结束值之间的转换。这些可能是坐标、颜色或对象的透明度。x

事实上,理论上,您可以应用不同的缓动函数来对不同的属性进行插值。希望这有助于阐明基本思想。

这里有一个很酷的例子(它使用了一个稍微不同的签名,但原理相同)来了解缓动与位置的关系。


编辑

这是我拼凑起来的一个小jsFiddle,用于演示 javascript 中的一些基本用法。请注意,top属性是使用反弹进行补间的,而left属性是使用四边形进行补间的。使用滑块模拟渲染循环。

由于easing对象中的所有函数都具有相同的签名,因此您可以将它们中的任何一个相互交换。现在大多数这些东西都是硬编码的(比如开始和结束值、使用的补间函数和动画的长度),但是在动画助手的真实示例中,你会想要通过在以下属性中:

  • 要更改的属性
  • 起始值(或者如果离开undefined则使用其当前值)
  • 终值
  • 动画的长度应该是
  • 对要使用的补间函数的引用。

动画引擎会在动画的持续时间内跟踪这些设置,并且在每个更新周期中,它会使用补间参数来计算属性的新值。

于 2011-11-29T21:05:33.683 回答
11

缓动函数是一种控制动画速度以提供所需效果(弹跳、放大和减慢等)的算法。

查看MSDN 对它们的更多详细信息。

于 2011-11-29T19:58:56.303 回答
8

我想发布我对这个老问题的答案,即使它有一个公认的答案。32bitkid做了必要的解释。我要补充的是基本的实际实现,因为我找不到(我也发布了一个关于它的问题)。

以这个简单的线性动画为例。我怀疑它需要任何解释,因为代码是不言自明的。我们计算一个不随时间变化的恒定增量值,并且在每次迭代中,我们增加框的位置。我们直接修改位置变量,然后将其应用到盒子上。

JSFiddle

var box = document.getElementById("box");

var fps           = 60;
var duration      = 2;                                   // seconds
var iterations    = fps * duration;                      // 120 frames
var startPosition = 0;                                   // left end of the screen
var endPosition   = window.innerWidth - box.clientWidth; // right end of the screen
var distance      = endPosition - startPosition;         // total distance
var posIncrement  = distance / iterations;               // change per frame
var position      = startPosition;                       // current position

function move() {
  position += posIncrement;              // increase position
  if (position >= endPosition) {         // check if reached endPosition
    clearInterval(handler);              // if so, stop interval
    box.style.left = endPosition + "px"; // jump to endPosition
    return;                              // exit function
  }
  box.style.left = position + "px";      // move to the new position
}

var handler = setInterval(move, 1000/fps); // run move() every 16~ millisecond
body {
    background: gainsboro;
}
#box {
    width: 100px;
    height: 100px;
    background: white;
    box-shadow: 1px 1px 1px rgba(0,0,0,.2);
    position: absolute;
    left: 0;
}
<div id="box"></div>


现在,让我们添加缓动。我们从使用linear(no-easing) 开始简单。它会产生与上面相同的动画,但方法不同。这一次,我们不会直接修改位置变量。我们要修改的是时间。

function linear(time, begin, change, duration) {
    return change * (time / duration) + begin;
}

首先,让我们谈谈参数。

  • time:经过的时间
  • begin: 属性的初始值(宽度、左侧、边距、不透明度等)
  • change:位移,(结束值 - 开始值)
  • duration:动画将花费的总时间

time并且duration直接相关。如果你有一个 2 秒的动画,你增加time它并将它传递给 easing 函数linear。该函数将返回一个位置,该位置指示框在给定时间应位于该位置。

假设我在 2 秒内将一个盒子从 0 移动到 100。如果我想获得盒子的位置,比如在 700 毫秒,我会linear通过以下方式调用该函数:

linear(0.7, 0, 100, 2);

这将返回35。动画开始后 700 毫秒,盒子的位置将在 35px。让我们看看这个在行动。

JSFiddle

var box = document.getElementById("box");

var fps           = 60;
var duration      = 2;                                   // seconds
var iterations    = fps * duration;                      // 120 frames
var startPosition = 0;                                   // left end of the screen
var endPosition   = window.innerWidth - box.clientWidth; // right end of the screen
var distance      = endPosition - startPosition;         // total distance
var timeIncrement = duration / iterations;
var position      = 0;
var time          = 0;

function move() {
    time += timeIncrement;
    position = linear(time, startPosition, distance, duration);
    if (position >= endPosition) {
        clearInterval(handler);
        box.style.left = endPosition + "px";
        return;
    }
    box.style.left = position + "px";
}

var handler = setInterval(move, 1000/fps);

function linear(time, begin, change, duration) {
    return change * (time / duration) + begin;
}
body {
    background: gainsboro;
}
#box {
    width: 100px;
    height: 100px;
    background: white;
    box-shadow: 1px 1px 1px rgba(0,0,0,.2);
    position: absolute;
    left: 0;
}
<div id="box"></div>


这段代码需要注意的部分是:

var timeIncrement = duration / iterations;
var time = 0;

function move() {
    time += timeIncrement;
    position = linear(time, startPosition, distance, duration);
    // ...

在第一个动画中,我们直接修改了位置变量。我们需要一个恒定的位置增量值。我们计算的方式是posIncrement = distance / iterations。通过缓动,我们不再修改位置变量,而是时间变量。所以我们需要一个时间增量值。我们以与位置增量相同的方式计算它,只是这次我们duration除以iterations。我们随着时间的增加增加时间并将时间传递给缓动函数,缓动函数返回我们盒子应该占据的下一个位置。

total distance / iterations (frames) = position change per frame
total duration / iterations (frames) = time change per frame

这是一些眼睛的图表。

缓动函数图


最后,一个easeInOutQuad 示例。

JSFiddle

var box = document.getElementById("box");

var fps           = 60;
var duration      = 2;                                   // seconds
var iterations    = fps * duration;                      // 120 frames
var startPosition = 0;                                   // left end of the screen
var endPosition   = window.innerWidth - box.clientWidth; // right end of the screen
var distance      = endPosition - startPosition;         // total distance
var timeIncrement = duration / iterations;
var time          = 0;
var position      = 0;

function move() {
  time += timeIncrement;
  position = easeInOutQuad(time, startPosition, distance, duration);
  if (position >= endPosition) {
    clearInterval(handler);
    box.style.left = endPosition + "px";
    return;
  }
  box.style.left = position + "px";
}

var handler = setInterval(move, 1000 / fps);

function easeInOutQuad(t, b, c, d) {
  if ((t /= d / 2) < 1) {
    return c / 2 * t * t + b;
  } else {
    return -c / 2 * ((--t) * (t - 2) - 1) + b;
  }
}
body {
    background: gainsboro;
}
#box {
    width: 100px;
    height: 100px;
    background: white;
    box-shadow: 1px 1px 1px rgba(0,0,0,.2);
    position: absolute;
    left: 0;
}
<div id="box"></div>

于 2016-07-21T20:39:00.203 回答
1

tl; dr 假设标准化 0-1 范围和强度支持的缓和功能的基本植入:

t = 归一化的输入值,所以从 0 到 1。

s = 强度,其中 1 是线性的,它越高,缓动效果越强。

# ease-in
result = t**s 

# ease-out
s = int(s)*2 + 1
result = s*((t-1)**s + 1)

# ease-inOut
result = t**s /(t**s + (1-t)**s)
于 2021-08-09T09:15:40.267 回答
0

它是从一种状态到另一种状态的属性(大小、形状、位置)转换。

下面是一些简洁的小图,描述了 jquery ui 提供的缓动功能。

http://jqueryui.com/demos/effect/easing.html

于 2011-11-29T19:59:51.537 回答