54

Say, if I'm doing the Ease-Out and then Ease-In animation of an object's movement from X1 coordinate to X2 coordinate over S steps at equal time intervals. Can some suggest the formula to calculate this movement's X coordinates?

Jan Feldmann
  • 378
  • 5
  • 13
ahmd0
  • 16,633
  • 33
  • 137
  • 233
  • 4
    Check out http://www.robertpenner.com/easing/, in particular the action script 2.0 source. From that you should be able to convert it to C#. – Matthew Nov 19 '12 at 20:57

6 Answers6

97

Personally, I'd rather use a function that gets a time in [0; 1] and output a value in [0; 1], so that we can apply the result to any type (2D vector, 3D vector, ...).

Solution 1

For the quadratic easing in/out, the curve is separated in two distinct functions depending on the value of t:

  • when t <= 0.5: f(x) = 2 * x * x with x in [0;0.5] (graph)
  • when t > 0.5: f(x) = 2 * x * (1 - x) + 0.5 with x in [0;0.5] (graph)

Here are the graphs:

graph - part 1
graph - part 2

Since the second function is also in [0;0.5], but t > 0.5 when we start to use it, we need to reduce t by 0.5.

This is the result, in C:

float InOutQuadBlend(float t)
{
    if(t <= 0.5f)
        return 2.0f * t * t;
    t -= 0.5f;
    return 2.0f * t * (1.0f - t) + 0.5f;
}

Solution 2 (Bézier)

Another interesting blend curve is the one given by Bézier, which have the advantage to be quite optimized (no if). Here is the curve from Wolfram:

Bezier curve

And here is the C code:

float BezierBlend(float t)
{
    return t * t * (3.0f - 2.0f * t);
}

Solution 3 (parametric function)

Another method proposed by @DannyYaroslavski is the simple formula proposed here.

It is parametric and gets a nice in/out acceleration and deceleration.

With alpha = 2, you get this function:

curve

Which translates in C like this:

float ParametricBlend(float t)
{
    float sqt = t * t;
    return sqt / (2.0f * (sqt - t) + 1.0f);
}
starball
  • 20,030
  • 7
  • 43
  • 238
Creak
  • 4,021
  • 3
  • 20
  • 25
  • In the last function I suppose that X is actually a T parameter of the funcrion, right ? – Coldsteel48 May 17 '17 at 18:05
  • In case anyone else in looking here are all 3 functions, in **JavaScript** and compressed . . . . . . . . . `function InOutQuad(n){return n<=.5?2*n*n:2*(n-=.5)*(1-n)+.5}` `function Bezier(t){return t*t*(3-2*t)}` `function Parametric(t){return t*t/(2*(t*t-t)+1)}` (Give 'em a number between 0 and +1 and they'll return a number between 0 and +1.) – ashleedawg Feb 02 '22 at 03:33
48

Quadratic ease out where:

t = current time
b = start value
c = change in value
d = duration

 function (float time, float startValue, float change, float duration) {
     time /= duration / 2;
     if (time < 1)  {
          return change / 2 * time * time + startValue;
     }

     time--;
     return -change / 2 * (time * (time - 2) - 1) + startValue;
 };

source: http://gizma.com/easing/

random_user_name
  • 25,694
  • 7
  • 76
  • 115
Toad
  • 15,593
  • 16
  • 82
  • 128
  • Toad, when you say `t = time` do you mean time from start of animation or time from previous frame ? – Sir Oct 23 '15 at 02:07
  • t goes from 0 - 1 where 0 is the beginning of the animation, and 1 is the end. For every keyframe, you should change the values and let t again go from 0 to 1 – Toad Oct 23 '15 at 14:17
  • 2
    What is the change in value? I don't understand where that comes from. – starbeamrainbowlabs Nov 20 '15 at 14:53
  • 1
    You first use the formula to go from keyframe1 to keyframe 2. (So b is keyframe1 value and c is keyframe 2 value). Then you let the t go from 0.0 to 1.0. By the time you are at 1.0 you repeat these steps, only now you use keyframe2 and keyframe 3 – Toad Nov 25 '15 at 13:42
  • This answer should get 1M thumbs ups. I looked 10 sources, found the same formula, but none of them mentioned what the hell t, b, c and d where. Thanks man – Milad.Nozari Feb 15 '16 at 10:38
  • ok, I kind of understand, this, but what do you do with the returned value? – Myke Dev Apr 19 '17 at 19:00
  • say you ease from the value b=8 to c=17. Then given the current t (time) in respect to the total duration(d) it returns the adjusted value which eases the motion instead of that it interpolates linearly – Toad Apr 21 '17 at 06:23
  • 1
    Say the startvalue = 3 and you want to ease to the value 5. Then the change in value is 2. So the change in value is the endvalue - the start value. – Toad Nov 12 '17 at 20:30
  • I was confused because I thought the return value was by how much the initial value (what the startValue is based on) should change, but instead it's what that initial value should become. "return change" threw me off. – TimSim Dec 19 '17 at 06:45
3

All the above solutions lack examples of usage.

Found good solution here:

 function animate({timing, draw, duration}) {

  let start = performance.now();

  requestAnimationFrame(function animate(time) {
    // timeFraction goes from 0 to 1
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) timeFraction = 1;

    // calculate the current animation state
    let progress = timing(timeFraction)

    draw(progress); // draw it

    if (timeFraction < 1) {
      requestAnimationFrame(animate);
    }

  });
}

Example of usage:

animate({
  duration: 1000,
  timing(timeFraction) { // here you can put other functions
    return timeFraction;
  },
  draw(progress) {
    elem.style.width = progress * 100 + '%';
  }
});

Other function:

function quad(timeFraction) {
  return Math.pow(timeFraction, 2)
}

More here

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Oleksandr Poshtaruk
  • 2,062
  • 15
  • 18
2

I got same problem: wanted to animate my chart (Ease in-out).

Brainstorm gave me two ways:

1) Trygonometric formula. Firstly, I wrote y=(sin(x/π*10-π/2)+1)/2,which analog is sin^2((5*x)/π)

float TrygoEase (float x) {
    float y=(float)Math.pow(Math.sin(5*x/Math.PI),2);
    return y;
}

2) Two parabolas. It was not hard. I just used y=2*x*x on [0;0.5], and y=-2(x-1)^2+1 on [0.5;1]

float ParabolEase(float x) {
    float y=2*x*x;
    if(x>0.5f){
        x-=1;
        y=-2*x*x+1;
    }
    return y;
} 

Use this ways for x=[0;1], what returns also y=[0;1].

Now You can compare this graphs:

enter image description here

Egor
  • 279
  • 5
  • 21
0

Here is a version with the amount of curvature as an argument, following this general solution linked to by Creak.

/*
* applyCurve: apply an S-curve to an input value.
* The highest positive curvature will result in a step from 0 to 1,
* the most negative curvature will result in a constant of 0.5.
*
* progress: the input value between 0 and 1,
* curvature: the amount of curvature between -1 and 1.
*  Negative values curve the other way, 0 applies no curvature.
*/

double applyCurve(double progress, double curvature) {
    assert(progress >= 0.0 && progress <= 1.0);
    assert(curvature >= -1.0 && curvature <= 1.0);

    if (curvature >= 0.0) {
        if (curvature > 0.99999) return progress > 0.5 ? 1.0 : 0.0;

        float exp = 1.0 / (1.0 - curvature); // find s-curve exponent
        return pow(progress, exp) / (pow(progress, exp) + pow(1.0 - progress, exp)); // apply s-curve
    } else {
        if (curvature < -0.99999) return 0.5;

        float exp = 1.0 + curvature; // find s-curve exponent
        return pow(progress, exp) / (pow(progress, exp) + pow(1.0 - progress, exp)); // apply s-curve
    }
}
Mattijs
  • 1,909
  • 3
  • 19
  • 28
0

This version allows you to use any ease in and ease out functions (EaseIn and EaseOut). Both functions must take a time value parameter from between 0 and 1, and return an eased time value between 0 and 1.

float EaseInOut(float t)
{
    if (t <= 0.5f)
    {
        return EaseIn(t * 2) * 0.5f;
    }
    else
    {
        t -= 0.5f;
        return (EaseOut(t * 2) * 0.5f) + 0.5f;
    }
}