Animating with Ease In/Out: Deriving the calculations for smooth animations using no calculus.

posted 2013-Aug-8
— updated 2024-May-8

The problem with Linear Interpolation

Animating an item directly from point A to B is simple when using “linear interpolation”. One simply moves the item at a constant rate for the duration of the animation:

A B Velocity Time

The “problem” with this, shown above, are the changes in the velocity of the object. Linear interpolation produces instantaneous, infinite acceleration and deceleration. Physical objects that humans interact with in the real world do not work that way. Objects always spend some time coming up to speed and—unless there’s a brick wall involved—some time slowing down at their destination.

Computer interfaces are more pleasing to humans when the velocities change. So let’s figure out how to do that. And let’s do so in a way that makes sense, and is easy to re-derive when you are stranded on a deserted island and need to come up with the answer.

Plotting Velocities

First, let’s describe our problem in a way that enumerates what we have to work with.

Let’s draw a diagram showing the velocities we want:

The items in black are what we have control over. The items in green we may need to work out.

Calculating the Distance from Velocity

Now, I said that there wouldn’t be any calculus involved. That’s mostly true, except that we’re going to use one helpful fact often taught in calculus class. The area under a velocity curve is equal to the distance traveled. At any time if we can figure out how much hashed area there is to the left, we know how far along we should be in our animation.

With that one tool in our belt we can revert to geometry to write the equations needed for our program. Let’s start at the beginning of our animation and work out what we need along the way.

Instead of deriving equations that work with specific start and end values, I want a single solution that given ta, tg, td, and the elapsed time tn can tell us what percent through the animation we should be. Then we can use that percentage to lerp between any start and end values. In addition to being a nicely generic solution, this is particularly useful when animating more than one property, such as a 3D position comprised of X, Y, and Z values. We perform the calculation to find the completion percent once, and then use that value to lerp each position value between the start and end points.

Given this desire, we want the total area under the velocity graph to be 1.0, corresponding a value of 100% complete at the end. The area is the sum of the area of the middle rectangle and end triangles:

area = da + dg + dd
1.0 = ½tav + tgv + ½tdv

Rearranging, we can find the maximum velocity for any given triplet of timings:

1.0 = (½ta + tg + ½td)v
v = 1.0 / (½ta + tg + ½td)
v = 1.0 / (tg + ½(ta + td))

Now, given any tn, how far through the animation should we be? Let’s break it down into the three sections:

In the accelerating and decelerating zones, while we can compute the current vn, it is easier to recognize that the smaller triangle is proportional to the full sized triangle, and that the ratio of the areas is thus the square of the ratio of the bases. That might be confusing in prose, but in math we can just say (when looking at the acceleration-so-far portion):

tn²    ½tnvn
——— = ———————
ta²    ½tav

We can then rearrange this to find just the area of the smaller triangle, expressed without needing to know vn:

      ½tn²tav    tn²v
½tnvn = ——————— = ——————
        ta²       2ta

We can find the area of the deceleration-remaining triangle similarly.

Armed with this knowledge, we can write code for finding the percent complete.

// elapsed - how much time has passed since the animation started
// accel   - how much time should be spent accelerating
// glide   - how much time should be spent at maximum speed
// decel   - how much time should be spent decelerating
function percentComplete(elapsed, accel, glide, decel) {
  // The following two values could be precomputed,
  // assuming unchanging values for accel, glide, and decel
  const totalTime = accel + glide + decel
  const maxVelocity = 1 / (glide + (accel + decel)/2)

  if (elapsed <= accel) {
    return maxVelocity * elapsed ** 2 / (2 * accel)
  } else if (elapsed <= (accel + glide)) {
    return maxVelocity * (accel / 2 + (elapsed - accel))
  } else if (elapsed < totalTime) {
    return 1.0 - maxVelocity * (totalTime - elapsed)**2 / (2 * decel)
  } else {
    return 1.0
net.mind details contact résumé other