什么是缓动函数?

发布于 2024-12-18 18:31:37 字数 176 浏览 7 评论 0原文

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

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

What is meant by easing function in the context of animation. It seems that dojo, jquery, silverlight, flex and other UI systems have the notion of easing function. I could not locate a good explanation of easing functions? Can anyone explain the concept of easing functions, or point a good explanation of them, I am interested in the concept not in the specific details of a framework?

Is easing strictly used for location or is it general and can be applied to any property of an object?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

非要怀念 2024-12-25 18:31:39

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

查看 MSDN 对它们的介绍,了解更多详细信息。

An easing function is an algorithm that controls the speed of an animation to give a desired effect (bouncing, zoom in and slow, etc.).

Check out what MSDN has to say about them for a little more detail.

聚集的泪 2024-12-25 18:31:39

我想发布我对这个老问题的答案,即使它有一个被接受的答案。 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>


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

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

首先,我们来谈谈参数。

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

timeduration 直接相关。如果您有 2 秒的动画,则增加时间并将其传递给缓动函数线性。该函数将返回一个位置,指示该框在给定时间应位于该位置。

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

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);
    // ...

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

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

这是一些适合眼睛的图表。

缓动功能graph


最后是一个 escapeInOutQuad 示例。

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>

I'd like to post my answer to this old question even though it has an accepted answer. 32bitkid has made the necessary explanation. What I'll add is the basic practical implementation, because I couldn't find one (which I also posted a question about it).

Take this simple linear animation, for example. I doubt it requires any explanations since the code is self-explanatory. We calculate a constant increment value which doesn't change over time and at each iteration, we increase the position of the box. We're modifying the position variable directly and then applying it on the box.

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>


Now, let's add easing. We start simple by using linear (no-easing). It'll result the same animation above, but the approach is different. This time, we won't be modifying the position variable directly. What we'll be modifying is time.

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

First, let's talk about the parameters.

  • time: the elapsed time
  • begin: initial value of a property (width, left, margin, opacity, etc.)
  • change: displacement, (end value - start value)
  • duration: total time the animation will take

time and duration are directly related. If you have a 2 second animation, you increase time and pass it to the easing function linear. The function will return a position which indicates that the box should be at that position at the given time.

Let's say I'm moving a box from 0 to 100 in 2 seconds. If I want to get the position of the box, say at the 700 millisecond, I'd call the linear function in the following way:

linear(0.7, 0, 100, 2);

which would return 35. 700 milliseconds after the animation starts, box's position will be at 35px. Let's see this in action.

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>


The parth that needs attention in this code is:

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

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

In the first animation, we directly modified the position variable. We needed a constant position increment value. The way we calculated that is posIncrement = distance / iterations. With easing, we no longer modify the position variable, but the time variable. So we need a time increment value. We calculate it the same way we did position increment, only this time we divide duration by iterations. We increase the time with time increment and pass the time to easing function, and easing function returns us the next position the box should occupy.

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

Here are some graph for the eye.

Ease function graph


And lastly, an easeInOutQuad example.

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>

筱武穆 2024-12-25 18:31:39

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)

tl;dr basic implantation of the ease function assuming normalized 0-1 range with strength support:

t = normalized input value, so from 0-1.

s = strength, where 1 is linear and the higher it is, the stronger the easing effect.

# 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)
怀里藏娇 2024-12-25 18:31:39

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

这里有一些简洁的小图,描述了 jquery ui 提供的缓动函数。

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

It is a property (size, shape, location) transition from one state to another.

Here are some neat little graphs describing the easing functions offered by jquery ui.

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

忆离笙 2024-12-25 18:31:38

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

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

我们的缓动函数将采用几个参数:

  • percentComplete:(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

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

Linear Easy

让我们看一下更多内容复杂的缓动函数,二次缓动:

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,这是我们指定的 startend 值之间实际距离的四分之一。

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

Quad-Ease-In

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

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

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

Ease out

还有一些可以缓入和缓出的函数:

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;
};

EaseInOut

此函数启动速度会很慢并缓慢结束,在中间达到最大速度。

您可以使用多种缓动/插值:线性、二次、三次、四重、五重、正弦。还有一些特殊的缓动函数,例如 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;
},

Elastic escape in

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

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

您会注意到,我避免明确谈论 xy 位置,因为缓动与位置本身没有任何具体关系嗯>。缓动函数仅定义起始值和结束值之间的过渡。这些可以是 x 坐标、颜色或对象的透明度。

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

这是一个非常很酷的示例(使用稍微不同的签名,但原理相同)了解缓动与位置的关系。


编辑

这是我整理的一些jsFiddle,用来演示一些基本的JavaScript 中的用法。请注意,top 属性使用反弹进行补间,left 属性使用四边形进行补间。使用滑块来模拟渲染循环。

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

  • 要更改的属性
  • 起始值(或者如果未定义,则使用其当前值)
  • 结束值
  • 动画的长度 对
  • 要使用的补间函数的引用。

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

An easing function is usually a function that describes the value of a property given a percentage of completeness. Different frameworks use slightly different variations, but the concept is easy to grasp once you get the idea, but it's probably best to look a few examples.

First lets look at the interface that all our easing functions will abide by.

Our easing functions will take several arguments:

  • percentComplete: (0.0 to 1.0).
  • elapsedTime: The number of milliseconds the animation has been running
  • startValue: the value to start at (or the value when the percent complete is 0%)
  • endValue: the value to end at (or the value when the percent complete is 100%)
  • totalDuration: The total desired length of the animation in milliseconds

And will return a number which represents the value the property should be set to.

Note: this is the same signature that jQuery uses for its easing functions, which I'll be borrowing for examples.

The easiest to understand is a linear ease:

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

And now to put this to use:

Lets say we had an animation that was going to go for 1000 milliseconds and was supposed to start at 0 and end at 50. Passing those values into our easing function should tell us what the actual value should be:

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

This is a pretty straight forward (no pun intended) tween. It is a simple linear interpolation. If you were to graph value vs time, it would be a straight line:

Linear ease

Lets take a look at a bit more complicated easing function, a quadratic ease in:

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

And lets look at the same results, using the same inputs as before:

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

Notice the values are very different than our linear ease. It starts out very slow, then accelerates to its ending point. At 50% completion of the animation it has only made it to a value of 12.5, which is one quarter of the actual distance between the start and end values we have specified.

If we were to graph this function it would look something like this:

Quad-Ease-In

Now lets take a look at a basic ease-out:

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

This essentially does the "opposite" acceleration curve of an ease in. It starts out fast and then decelerates to its ending value:

Ease out

And then there are functions that ease both in and out:

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;
};

EaseInOut

This function will start out slow and end slow, reaching its maximum velocity in the middle.

There are a bunch of easing/interpolations that you can use: Linear, Quadradic, Cubic, Quart, Quint, Sine. And there are specialty easing functions like Bounce and elastic, which have their own.

For example, an elastic ease in:

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;
},

Elastic ease in

Perhaps somebody else can explain the actual math part behind the interpolation, because honestly I'm not a math wiz. But that's the basic principle of the easing functions themselves.

When you start a tween/animation, the animation engine remembers the start and end values you want. Then each time it updates, its figures out of how much time has passed. It call the supplied easing function with the values to figure out the value the property should be set to. As long as all of the easing functions implement the same signature, they can be swapped out with ease, and the core animation engine doesn't have to know difference. (Which makes for an excellent separation of concerns).

You'll notice that I've avoided talking about x and y positions explicitly, because easing doesn't have anything specifically to do with position per se. An easing function just defines a transition between a start and end values. Those could be x coordinates, or a color, or the transparency of an object.

And in fact, in theory, you could apply different easing function to interpolate for different properties. Hopefully this helps shed some light on the basic idea.

And here is a really cool example (that uses a slightly different signature, but is the same principle) to play with to get the idea of how easing relates to position.


Edit

Here is a little jsFiddle I threw together to demonstrate some of the basic usages in javascript. Notice that the top property is tweened using bounce, and the left property is tweened using a quad. Use the slider to simulate the render loop.

Since all the functions in the easing object have the same signature, you can swap any of them out for each other. Right now most of these things are all hard-coded (things like start and end values, the tween functions that are used and the length of the animation), but in a real-world example of a animation helper, you would want to pass in the following properties:

  • The property to be changed
  • The start value (or if left undefined then use its current value)
  • The end value
  • The length the animation should be
  • The reference to the tweening function you want to use.

The animation engine would keep track of these settings for the duration of the animation and during every update cycle, it would use the tweening argument to calculate the properties new value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文