Command Palette

Search for a command to run...

Hectal
Course outline
Topic 9.2Intermediate ~22 min · step 2 of 6

Easing and Tweening

In one line

Move a value from A to B over a set time along a curve that starts slow, ends slow or overshoots. You write the tween by hand, then compare it with damp and a spring when the target keeps changing.

0/6 · 0%

Think of it like this

A train does not go from standing to 100 km/h in one instant, nor stop dead at the platform. It speeds up, cruises and brakes. A value that moves at one steady speed feels like a machine. One that eases in and out feels alive.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

lerp
Linear interpolation: a + (b - a) * t. A straight-line blend between two values.
easing
A function that bends t (0 to 1) so motion speeds up or slows down.
tween
An animation that moves a value from one number to another over a set time.
damping
Moving a fixed share of the remaining distance per second, so the motion slows as it arrives.
spring
A moving value with a velocity that is pulled towards a target. It can overshoot or not (critically damped).

Step by step

01Read the table

The first example prints five easings at t = 0, 0.25, 0.5, 0.75, 1, and at two values outside that range. Every curve is 0 at the start and 1 at the end. easeOutCubic is already 0.578 at t = 0.25. easeOutBack passes 1 (1.088 at t = 0.5) and comes back.

02Watch the balls

Five balls travel the same road in 2 s, one per easing, then wait 1 s and repeat. They leave and arrive together, and differ in between.

03Frame rate

The second example chases a target for exactly one second at 30, 60 and 144 fps. The fixed-fraction version gives 0.958, 0.998 and 1.000. damp gives 0.998 each time.

04Change the target

The third example moves the target from 1 to 2 at frame 30. The tween restarts and its speed jumps from 0.78 to 3.32 units a second. damp jumps too (0.31 to 5.99) because it reacts at once. The spring bends: 0.40 to 1.85.

Try it yourself

  1. 1

    Clamp t

    In the first example, remove the Math.min(..., 1) in the loop. The balls fly past the end during the 1 s wait and easeOutBack goes far beyond. That is the 1.5 column of the table at work.

  2. 2

    Faster damp

    In the second example change 6.3 to 12.6. The damp column prints 1.000 in all three rows (the lerp column does not change), and the formula line prints 0.1894 = 0.1894.

Code & diagrams

Five easing curves New tab

The table is printed first. In the picture, the top ball (grey) is linear.

idle

Console

Run the scene to see its console.

Expected console output

t                     0   0.25    0.5   0.75      1    1.5   -0.5
linear            0.000  0.250  0.500  0.750  1.000  1.500 -0.500
easeInQuad        0.000  0.063  0.250  0.563  1.000  2.250  0.250
easeOutCubic      0.000  0.578  0.875  0.984  1.000  1.125 -2.375
easeInOutCubic    0.000  0.063  0.500  0.938  1.000  1.500 -0.500
easeOutBack       0.000  0.817  1.088  1.064  1.000  1.763 -4.289
clamp(1.5, 0, 1) = 1 | lerp(2, 10, 0.25) = 4
Lerp per frame versus damp New tab

No canvas: pure numbers, so you can read them.

idle

Console

Run the scene to see its console.

Expected console output

fps   lerp 0.1 per frame   damp(lambda 6.3, delta)
 30          0.958              0.998
 60          0.998              0.998
144          1.000              0.998
damp is lerp(x, y, 1 - exp(-lambda * dt)): 0.0997 = 0.0997
A tween by hand, and a moving target New tab

The target jumps at frame 31. Each cell is the value and, in brackets, its speed in units a second.

idle

Console

Run the scene to see its console.

Expected console output

target 1 until frame 30, then 2. Each cell: value (speed in units per second)
frame 30  tween 0.875 (0.78)    damp 0.950 (0.31)    spring 0.944 (0.40)   
frame 31  tween 0.930 (3.32)    damp 1.050 (5.99)    spring 0.975 (1.85)   
frame 32  tween 0.984 (3.21)    damp 1.141 (5.42)    spring 1.022 (2.80)   
frame 60  tween 1.859 (0.87)    damp 1.948 (0.33)    spring 1.943 (0.41)   
frame 90  tween 2.000 (0.00)    damp 1.997 (0.02)    spring 1.998 (0.01)   

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

A zero duration

In the third example, call createTween(0, 1, 0).

terminal
$ Run
── what you'll see ──
frame 30 tween NaN (NaN) damp 0.950 (0.31) spring 0.944 (0.40)
Every tween cell prints NaN; the damp and spring cells are unchanged.

Myth vs fact

Myth

Easing makes the animation take longer.

Fact

It does not change the duration. It changes where along the road the value is at each moment.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    damp is the exact solution of x' = -lambda (x - target) over a step, so the result does not depend on how you cut the time into frames. Compare x += (y - x) * 0.1, whose result does.

  • ▸

    CSS has the same curves: transition-timing-function: ease-out and cubic-bezier(...). For DOM animation, the Web Animations API (element.animate) can run off the main thread. Inside a WebGL scene you animate in your loop, so you write the curve in JavaScript.

  • ▸

    The tween here holds no state outside elapsed, so it is easy to test: call tick(0.5) and compare with from + (to - from) * ease(0.5).

Remember this

  1. 1

    The problem this solves: lerp(a, b, t) moves a value in a straight line. Cameras, panels and colours that move like that feel stiff. And if the target keeps changing (a mouse, a slider) a fixed-time animation has to restart again and again.

  2. 2

    Lerp is linear interpolation: a + (b - a) * t, where t runs from 0 to 1. At t = 0.25 it is a quarter of the way. THREE.MathUtils.lerp(2, 10, 0.25) is 4.

  3. 3

    Easing bends t before it is used: value = a + (b - a) * ease(t). easeInQuad is t * t (slow start). easeOutCubic is 1 - (1 - t)^3 (fast start, gentle stop). easeInOutCubic does both. easeOutBack overshoots, then settles. A good easing maps 0 to 0 and 1 to 1, so the animation starts and ends exactly where you say.

  4. 4

    A tween is a value plus a timer: it keeps elapsed, adds delta each frame, clamps elapsed to duration, and returns from + (to - from) * ease(elapsed / duration). The clamp matters: outside 0 to 1 many easings shoot far away (the table shows easeOutBack(-0.5) = -4.289).

  5. 5

    When the target keeps changing, a tween must restart from the current value, which gives a visible kink in the speed. Two tools avoid that. damp (THREE.MathUtils.damp(x, y, lambda, dt)) moves a fraction 1 - exp(-lambda * dt) of the remaining gap each frame, so it is frame-rate independent and never needs a duration. A spring keeps a velocity, so a new target bends the path smoothly.

  6. 6

    **Why not x += (target - x) * 0.1?** That is a fraction per frame, so it is frame-rate dependent (Topic 9.1). After exactly one second it has covered 95.8% at 30 fps and 99.8% at 60 fps. damp covers the same 99.8% at any frame rate.

Explain it without notes

01

Why must a tween clamp elapsed to the duration?

02

A slider moves the camera's target every frame. Would you use a tween or damp, and why?

Practice

01

Write createTween(from, to, duration, ease) with a tick(delta) that returns the value, and show that tick(0.5) for from = 0, to = 1, duration = 1 with easeOutCubic is 0.875.

02

A sphere should follow a mouse x position with damp. Write the frame code with lambda 8.

Trade-offs

  • ↔

    A tween is predictable (it ends at a known time) but cannot follow a moving target. Damp follows anything but never quite arrives, so stop it when the gap is tiny.

Done when you can

  • Write lerp and two easing functions from memory

  • Write a tween with a clamped elapsed time

  • Say why a fixed fraction per frame depends on the frame rate and damp does not

  • Choose between a tween, damp and a spring for a given job